Christian Wagnsonner

Published: 79 articles

CRM 2011 – Metadaten auslesen (z.B. von Picklist)

CRM hat die Angewohnheit, dass es nicht den selektierten Wert (z.B. in einer Picklist) speichert, sondern den dahinterliegenden (internen) Code. Das mag durchaus Sinn machen, erschwert einem aber die Arbeit, wenn man programmatisch etwas bearbeiten möchte

Nachfolgend eine Funktion, welche die Metadaten entsprechend umwandelt, also z.B. selektierter Text in die interne Nummer.

[sourcecode language=“csharp“]
/// <summary>
/// Gets the metadata value.
/// </summary>
/// <param name="organization">The organization.</param>
/// <param name="entity">The entity.</param>
/// <param name="attribute">The attribute.</param>
/// <returns></returns>
public static string GetMetadataValue(contracts.IOrganization organization,Entity entity, string attribute)
{

IOrganizationService _service = OrgService(organization);
RetrieveAttributeRequest attributeRequest = new RetrieveAttributeRequest
{
EntityLogicalName = entity.LogicalName,
LogicalName = attribute,
RetrieveAsIfPublished = true
};

RetrieveAttributeResponse attMetadata = (RetrieveAttributeResponse)_service.Execute(attributeRequest);
object oAttribute = entity[attribute];

string sReturn = string.Empty;
if (oAttribute.GetType().Equals(typeof(Microsoft.Xrm.Sdk.OptionSetValue)))
{

OptionMetadata[] optionList = null;
// Access the retrieved attribute.
//’Microsoft.Xrm.Sdk.Metadata.PicklistAttributeMetadata‘
if (attMetadata.AttributeMetadata.GetType().FullName.Contains("PicklistAttributeMetadata"))
{
PicklistAttributeMetadata retrievedPicklistAttributeMetadata =
(PicklistAttributeMetadata)attMetadata.AttributeMetadata;
// Get the current options list for the retrieved attribute.
optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
}
else if (attMetadata.AttributeMetadata.GetType().FullName.Contains("StatusAttributeMetadata"))
{
StatusAttributeMetadata retrievedPicklistAttributeMetadata =
(StatusAttributeMetadata)attMetadata.AttributeMetadata;
// Get the current options list for the retrieved attribute.
optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
}
else if (attMetadata.AttributeMetadata.GetType().FullName.Contains("StateAttributeMetadata"))
{
StateAttributeMetadata retrievedPicklistAttributeMetadata =
(StateAttributeMetadata)attMetadata.AttributeMetadata;
// Get the current options list for the retrieved attribute.
optionList = retrievedPicklistAttributeMetadata.OptionSet.Options.ToArray();
}
else
return string.Empty;
// get the text values
int i = int.Parse((oAttribute as Microsoft.Xrm.Sdk.OptionSetValue).Value.ToString());
for (int c = 0; c < optionList.Length; c++)
{
OptionMetadata opmetadata = (OptionMetadata)optionList.GetValue(c);
if (opmetadata.Value == i)
{
sReturn = opmetadata.Label.UserLocalizedLabel.Label;
break;
}
}

}
else if (oAttribute.GetType().Equals(typeof(Microsoft.Xrm.Sdk.Money)))
{
sReturn = (oAttribute as Microsoft.Xrm.Sdk.Money).Value.ToString();
}
else if (oAttribute.GetType().Equals(typeof(Microsoft.Xrm.Sdk.EntityReference)))
{
sReturn = (oAttribute as Microsoft.Xrm.Sdk.EntityReference).Name;
}
else
{
sReturn = oAttribute.ToString();
}
if (sReturn == null || sReturn.Length == 0)
sReturn = "No Value";
return sReturn;
}

[/sourcecode]

Ich hoffe, ich konnte euch damit helfen 🙂

Cheers,
Chris

WCF/Soap Service aus Javascript konsumieren

In einem aktuellen Projekt habe ich die Aufgabe bekommen, ein WCF Service zu erstellen, welches dann mittels Javascript konsumiert werden können soll.
Doch ein SOAP-Service aus Javascript konsumieren ist eine recht aufwendige Geschichte und die gefundenen Tutorials im Internet oft alles andere als einfach verständlich.

Um das Service anzusprechen, verwende ich einen XMLHttpRequest, welchem man einen SoapBody/XML und eine SoapAction mitgeben muss. Um das SOAP-XML nicht selbst produzieren zu müssen, was ja auch gar nicht so einfach wäre, habe ich SOAP UI (http://www.soapui.org/) verwendet. Dort müsst Ihr nur das WSDL des Service eingeben und bekommt für jede Methode, die euer Service zur Verfügung stellt, einen Request (welcher das XML enthält) erzeugt:

Hier seht Ihr auch schon das XML, welches Ihr in eurer JS-Methode verwenden könnt, z.B.:

[sourcecode language=“xml“]
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:Login>
<!–Optional:–>
<tem:organization>?</tem:organization>
</tem:Login>
</soapenv:Body>
</soapenv:Envelope>
[/sourcecode]

Die SoapAction findet Ihr, wenn Ihr euch euer WSDL anseht.

In meinem Beispiel lautet diese: http://tempuri.org/IAuthentication/Login

Nun geht’s ans Javascript. Ich habe mir hierzu eine kleine Helper-Klasse geschrieben.

[sourcecode language=“javascript“]
var Utils = new function () {

var Request = new XMLHttpRequest();
var fbFunction = null;

this.CallWS = function CallWS(soapHeader, feedbackFunction) {

// Create CRM Request
fbFunction = feedbackFunction;
var url = ‚http://localhost/crm.service/Operation.svc‘;

Request.open("POST", url, true);
Request.setRequestHeader("SOAPAction", soapHeader.SoapAction);
Request.setRequestHeader("Accept", "application/xml");
Request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
Request.onreadystatechange = WSFinished;

Request.send(soapHeader.SoapXml);
}

function WSFinished() {

if (Request.readyState == 4) {

if (Request.status == 200) {

// parse webservice result
Result.Xml = Request.responseXML;
Result.ParseResult();
}
else {

Result.StatusCode = -2;
Result.StatusMessage = Request.responseText;
}
fbFunction(Result);
}
}
}

var SoapUtils = new function () {

var SoapHeader = new function () {
this.SoapXml = null;
this.SoapAction = null;
}

this.GetSoapHeaderForLogin = function (org) {
SoapHeader.SoapXml = "<soapenv:Envelope xmlns:soapenv=’http://schemas.xmlsoap.org/soap/envelope/‘ xmlns:tem=’http://tempuri.org/‘><soapenv:Header/><soapenv:Body><tem:Login><tem:organization>" + org + "</tem:organization></tem:Login></soapenv:Body></soapenv:Envelope>";
SoapHeader.SoapAction = "http://tempuri.org/IAuthentication/Login";
return SoapHeader;
}

}
[/sourcecode]

Der Aufruf sieht dann wie folgt aus:

[sourcecode language=“javascript“]
var header = SoapUtils.GetSoapHeaderForLogin("xyz");
Utils.CallWS(header, LoginFinished);
[/sourcecode]

Nachdem ich diese Webservice-Methoden mehrmals in meinem Projekt aufrufen muss, habe ich das SoapXML und die SoapAction in eine eigene Methode gepackt (SoapUtils), welche ich mittels SoapUtils.GetSoapHeaderForLogin("xyz"); aufrufe. Die zurückgelieferte Informationen gebe ich dann der CallWS-Methode mit (Utils.CallWS(header, LoginFinished);) und gebe zudem als zweiten Parameter eine Methode bekannt, welche aufgerufen werden soll, sobald der Webservice-Call fertig ist.

Wichtig: Ein Webservice-Aufruf ist immer asynchron! Es gibt zwar Spielereien (ähnlich Thread.Sleep() in C#), diese sollten jedoch erfahrungsgemäß nicht verwendet werden.

Wenn Ihr euch den Source-Code genauer anseht, werdet Ihr feststellen, dass ich in der Methode „CallWS“ dem Request.onreadystatechange-Event subscribed habe. In der Methode WSFinished() prüfe ich nun State des Requests und Status des Requests und erzeuge ein Result-Objekt, welches ich der Feedback-Funktion (übergeben bei CallWS) retour liefere. Hier seid Ihr selbst gefordert, euer Webservice wird naturgemäß andere Resultate erzeugen als meines.

Ich hoffe, ich konnte euch ein wenig weiterhelfen 🙂

Cheers,
Chris

Microsoft Unity 2.0 – Einstieg leicht gemacht

Statische Referenzen auf Assemblies haben Vor- und Nachteile. Wer im Zeitalter von dynamischen Systemen und verteilten Anwendungen flexibel sein möchte nutzt oft Dependency Injection Frameworks. Unity ist so eines.

Unity befindet sich aktuell in Version 2.0. Hier findet Ihr den Download: http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=9093

In einem neuen Projekt habe ich die Aufgabe gestellt bekommen, ein manndantenfähiges Webservice zu entwickeln, welches unabhängig vom Manndanten, bestimmte Methoden zur Verfügung stellen soll. Es soll Methoden geben, welche für alle Manndanten gleich sind, aber auch Spezialimplementierungen für einen bestimmten Manndanten zulässt.

Nachdem ich möglichst flexibel sein wollte, habe ich mir folgende Architektur überlegt:

Wie man sieht, kennt das Webservice lediglich die Verträge/Interfaces. Unity soll für mich entscheiden, welche Implementierung (KundeA, KundeB, KundeDefault) „ziehen“ soll. Man könnte natürlich auch noch KundeA und KundeB (die Spezialimplementierungen) auf KundeDefault referenzieren lassen, habe ich aber vorläufig nicht gemacht.

Um Unity einzubinden, muss man dem ausführenden Projekt (in meinem Fall „WcfService“) folgende Assemblies hinzufügen: Microsoft.Practices.Unity und Microsoft.Practices.Unity.Configuration.

In der web.config habe ich eine eigene Section eingerichtet und lasse diese auf die Datei Unitiy.config verweisen:

[sourcecode language=“xml“]
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity configSource="Unity.config" />
[/sourcecode]

In der Unity.config habe ich folgende Zeilen hinzugefügt:

[sourcecode language=“xml“]
<?xml version="1.0" encoding="utf-8" ?>
<unity>
<typeAliases>

<typeAlias alias="IVerkaufschance"
type="Contracts.IVerkaufschance, Contracts" />

<typeAlias alias="VerkaufschanceKundeDefault"
type="KundeDefault.Verkaufschance, KundeDefault" />

<typeAlias alias="VerkaufschanceKundeA"
type="KundeA.Verkaufschance, KundeA" />

<typeAlias alias="VerkaufschanceKundeB"
type="KundeB.Verkaufschance, KundeB" />

</typeAliases>

<containers>
<container>
<types>

<!– Default (un-named) mapping for IVerkaufschance to VerkaufschanceKundeA –>
<type type="IVerkaufschance" mapTo="VerkaufschanceKundeDefault"></type>

<!– Named mapping for IVerkaufschance to VerkaufschanceKundeA –>
<type type="IVerkaufschance" mapTo="VerkaufschanceKundeA" name="KundeA"></type>

<!– Named mapping for IVerkaufschance to VerkaufschanceKundeB –>
<type type="IVerkaufschance" mapTo="VerkaufschanceKundeB" name="KundeB"></type>

</types>
</container>
</containers>
</unity>
[/sourcecode]

Wie man sieht, habe ich drei Typen angelegt. Die Default-Implementierung (un-named mapping, hier fehlt das „name“-Attribut) für den Vertrag „IVerkaufschance“ und die Spezialimplementierungen für KundeA und KundeB (named mappings). Zudem muss man natürlich die Typen für Vertrag und Implementierungen bekannt geben, dies geschieht über die TypeAliases.

Wie man im Architektur-Diagramm erkennt, hat das WcfService keine Kenntnis über die Implementierungen für Default, KundeA und KundeB. Daher ist es notwendig, in den jeweiligen Projekten, im Post-Build-Event die erzeugte Assembly in das bin-Verzeichnis der ausführenden Applikation (WcfService) zu kopieren. Das geht am einfachsten mittels XCOPY

xcopy "$(SolutionDir)KundeAbindebugKundeA.dll" "$(SolutionDir)WcfServicebin" /Y

Im Code müsste man nun die Unity-Container (in meinem Fall gibt es nur einen) nach den entsprechenden Typen durchsuchen. Um das zu vereinfachen, habe ich eine Helperklasse geschrieben:

[sourcecode language=“csharp“]
public interface IContainerAccessor
{
IUnityContainer Container { get; }
}

public class Dependency
{
public static void Register(IUnityContainer container)
{
UnityConfigurationSection config = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
config.Containers.Default.Configure(container);
}

public static T Resolve<T>()
{
return Container.Resolve<T>();
}

public static T Resolve<T>(object name)
{
try
{
return Container.Resolve<T>(name.ToString());
}
catch (ResolutionFailedException)
{
return Container.Resolve<T>();
}
}

private static IUnityContainer _container;
public static IUnityContainer Container
{
get
{
// Testing only – see Set method below
if (_container != null) return _container;

var context = HttpContext.Current;

// The most likely situation where this occurs is because we are on a different thread. These threads
// are not part of the ‚current‘ context, hence explosion
if (context == null)
{
try
{
IUnityContainer c = new UnityContainer();
Register(c);
return c;
}
catch
{
// When running from the TEST projects, the above fails.
return null;
}
}

// Get it from global. This is cached in the Application, so is the ideal scenario
var accessor = context.ApplicationInstance as IContainerAccessor;
if (accessor == null)
{
return null;
}

var container = accessor.Container;

if (container == null)
{
throw new InvalidOperationException("No Unity container found");
}

return container;
}

// Only for testing purposes
set
{
_container = value;
}
}

}
[/sourcecode]

Meinem Webserviceprojekt (WcfService) habe ich nun ein ein Wcf Service hinzugefügt, welches ich „Verkaufschance“ benannt habe. Im generierten Code der Verkaufschance.svc.cs-Klasse habe das Interface auf „contracts.IVerkaufschance“ abgeändert und das generierte Interface weggeschmissen und die Copy-Funktion, welche im Interface definiert ist, implementiert.

[sourcecode language=“csharp“]
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Verkaufschance" in code, svc and config file together.
public class Verkaufschance : contracts.IVerkaufschance
{
public string Copy(string organization)
{
return Dependency.Resolve<contracts.IVerkaufschance>(organization).Copy("something");
}
}
[/sourcecode]

In der Copy-Funktkion kommt nun Unity zum Einsatz. Mittels Dependency.Resolve löse ich gegen die erzeugte Konfiguration (Unity.config) die entsprechende Assembly auf. Der Parameter „organization“ ist der Schlüssel zu den Named-Mappings. Findet er den Schlüssel nicht, zieht die Default-Implementierung.

Die Implementierung, für KundeA, sieht dann wie folgt aus:

[sourcecode language=“csharp“]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace KundeA
{
class Verkaufschance : Contracts.IVerkaufschance
{
#region IVerkaufschance Members

public string Copy(string organization)
{
return "Sonderlösung für " + organization;
}

#endregion
}
}
[/sourcecode]

Ich habe somit einen Vertrag (im Contracts-Projekt), welchen ich für die Implementierung des Webservices und für die eigentliche Implementierung der Methoden verwende. So ist sichergestellt, dass die Implementierung für jeden Manndanten gleich ist bzw. die gleichen Methoden zur Verfügung stellt.

Ich hoffe ich konnte euch damit den Einstieg in Unity etwas erleichtern.

Cheers
Chris

Internal Methods mit Unittests testen

Jeder der anfängt sich mit Unittests zu beschäftigen wird sich wahrscheinlich früher oder später fragen wie man interne Methoden testen kann. Es ist ganz Einfach!

Ich habe eine Class-Library (Implementation) erstellt und auch Visual Studio 2010 Test Project (API.Test).

Nun gehe zur AssemblyInfo meiner Class-Library und füge folgenden Code hinzu:

[sourcecode language=“csharp“]
//Set internal methods visible
//API.Test ist der Name der Assembly
[assembly:InternalsVisibleTo("API.Test")]
[/sourcecode]

Nun könnt ihr sämtliche internen Methoden im Projekt API.Test aufrufen und testen!

Viel Spass!
Waldi

Code Analyse mit Visual Studio 2010 – Teil 1

„Mein Code ist sauber“ diese Aussage wird sicher jeder
Programmierer schon gehört haben und auch schon mal gesagt haben. Nur leider
trifft das nicht sehr oft zu! Deshalb stelle ich euch kurz die in der Premium
und Ultimate Edition enthaltene Code Analyse vor.

Spätestens wenn ihr in einem Team arbeitet wo die Kollegen ein unterschiedliches Level besitzen werdet ihr die Code Analyse zu lieben anfangen. Auch wenn sie nicht vor allen Dummheiten schützt  wird sie euch das Leben  einiges erleichtern.

Ich habe ein Demo Projekt erstellt mit einer Class Library
und eine Demo-Methode. Nun aktivieren wir die Code Analyse:

Im Dropdown stehen schon mehrere rulesets zur Verfügung ich wähle
das passende aus und kompiliere mein Projekt, der folgenden(recht dummen) Code enthält:

[sourcecode language=“csharp“]
public int CreateCodingAnalysisError(string input)
{
int rv = 0;
int.TryParse(input.ToString(), out rv);
return rv;
}
[/sourcecode]

Nach dem erfolgreichen kompilieren erscheint folgende Warnung

Jetzt gibt es zwei Möglichkeiten diese Warnung zu beheben. Die erste Möglichkeit ist diese Warnung zu Ignorieren:

Rechtklick auf die Warnung und wähle Suppress Message und eine der zwei Optionen „In Source“ oder „In Projekt Suppression File“ aus. Ich wähle die Option „In Source“.

Nachdem ich „In Source“ geklickt habe wird etwas Code hinzugefügt. Im Grunde sollten keine Warnung ignoriert werden und wenn, ist es ideal wenn sie den Grund dafür dokumentieren.

Die zweite Möglichkeit ist den Code entsprechend auszubessern Microsoft bietet zu jeder Warnung entsprechende Artikel inkl. Erklärungen in der MSDN an, einfach nach der Nummer suchen z.B.: CA 1806. Der korrekte Code sieht wie folgt aus:

[sourcecode language=“csharp“]
public int CreateCodingAnalysisError(string input)
{
int rv = 0;
if (int.TryParse(input.ToString(), out rv))
return rv;
else
return -1;
}
[/sourcecode]

Im Teil 2 zeige ich euch wir ihr euer eigenes ruleset erstellen könnt und welche weiteren Möglichkeiten es gibt.

Viel Spass!

Waldi

Einfacher Import in CRM 2011 von SQL Datenbank (ADO.NET Entity Framework)

Heute hat mich ein typisches Migrationsprojekt erreicht. Eine Marketinganwendung des Kunden soll durch CRM 2011. Dafür ist es zunächst notwendig, die Daten in CRM zu importieren. Erst danach sollen eventuell fehlende Features, etc. besprochen/entwickelt werden.

Heute zeige ich euch, wie man mittels den von CRM zur Verfügung gestellten Webservice-Schnittstellen Firmen aus einer SQL-Datenbank möglichst einfach in CRM importieren kann.

Um den Zugriff auf den SQL-Server so einfach wie möglich zu halten, habe ich mich dafür entschieden auf ADO.NET Entity Data Model zu setzen und habe dem Model sämtliche Tabellen der Quell-Datenbank hinzugefügt.

Nun lässt sich im C# Code ganz einfach auf die Datenbank zugreifen, z.B.

[sourcecode language=“csharp“]
conventoEntities context = new conventoEntities();
var firmen = (from p in context.Tbl_Firmen
orderby p.Firmenname1
select p);
[/sourcecode]

Im nächsten Schritt habe ich die notwendigen CRM-Webservices hinzugefügt. Im nachfolgenden Beispiel wird das Discovery und das Organization-Webservice benötigt. Eine genauere Beschreibung der Services findet ihr u.a. hier:

http://technet.microsoft.com/de-ch/library/gg309557(en-us).aspx
http://technet.microsoft.com/de-ch/library/gg328127(en-us).aspx
http://technet.microsoft.com/de-ch/library/microsoft.xrm.sdk.discovery.idiscoveryservice(en-us).aspx
http://technet.microsoft.com/de-ch/library/microsoft.xrm.sdk.iorganizationservice(en-us).aspx

Die Webservices, sind in der Standard-CRM-Installation enthalten, findet Ihr nach folgendem Schema:
http://[servername]:[port]/[Organisation]/XRMServices/2011/Organization.svc
http://[servername]:[port]/[Organisation]/XRMServices/2011/Discovery.svc

Wie man eine WebReferenz hinzufügt, habe ich in einem meiner letzten Blogs beschrieben.

Bevor wir zum eigentlichen Import-Code kommen, sei erwähnt, dass ich die Late-Binding-Variante verwendet habe. Man kann auch das sog. Early-Binding verwenden, das ist jedoch nicht Scope dieses Blog-Eintrags. Eventuell widme ich dem Thema noch später im Laufe des Projekts. Falls das so ist, werde ich diesen Artikel aktualisieren oder erweitern.

Nachfolgend nun das eigentliche Code-Snippet, welches folgende Aufgaben erledigt:

  1. Auslesen sämtlicher Firmen aus der Quell-Datenbank (mittels ADO.NET Entity Framework)
  2. Loopen durch alle Firmen
  3. CRM-Entitäts-Record erzeugen (Account/Firma)
  4. Eigenschaften mappen
  5. Einfacher Dublettencheck
  6. Einfügen / Aktualisieren

[sourcecode language=“csharp“]
static void Main(string[] args)
{
xxxEntities context = new xxxEntities();
var firmen = (from p in context.Tbl_Firmen
orderby p.Firmenname1
select p);

foreach(var firma in firmen)
{
Guid pk;
bool insert = InsertOrUpdate(firma.Firmenname1, out pk);

Entity account = new Entity("account");
account["name"] = firma.Firmenname1;
account["address1_line1"] = firma.Strasse;
account["address1_postalcode"] = firma.PLZ;
account["address1_city"] = firma.Ort;
account["telephone1"] = firma.Telefon;

account["telephone2"] = firma.Telefon1;
account["fax"] = firma.Telefax;
account["emailaddress1"] = firma.EMail;
account["emailaddress2"] = firma.EMail1;
//account["industrycode"] = firma.Branche1;
account["address1_county"] = firma.Bundesland;
account["address1_country"] = firma.Land;
//account["revenue"] = firma.Umsatz_Tsd;

if (insert)
Helper.OrgService.Create(account);
else
{
account["accountid"] = pk;
Helper.OrgService.Update(account);
}
}
}

private static bool InsertOrUpdate(string accountName, out Guid pk)
{
pk = new Guid();
var accounts = (from a in Helper.OrgContext.CreateQuery("account")
where (string) a["name"] == accountName
select a).FirstOrDefault();

if (accounts != null)
pk = new Guid(accounts["accountid"].ToString());

return pk == new Guid();
}
[/sourcecode]

Nachfolgend noch der Source meiner kleinen Helper-Klasse, welcher nur zwei Properties zum Arbeiten mit dem Organization-Webservice beinhaltet:

[sourcecode language=“csharp“]
public static class Helper
{
private static IOrganizationService _orgService;
public static IOrganizationService OrgService
{
get
{
if(_orgService == null)
{
ServerConnection serverConnect = new ServerConnection();
ServerConnection.Configuration config = serverConnect.GetServerConfiguration();
OrganizationServiceProxy proxy = new OrganizationServiceProxy(config.OrganizationUri, config.HomeRealmUri, config.Credentials, config.DeviceCredentials);

proxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());
_orgService = (IOrganizationService)proxy;
}
return _orgService;
}
}

private static OrganizationServiceContext _orgContext;
public static OrganizationServiceContext OrgContext
{
get
{
if(_orgContext == null)
{
_orgContext = new OrganizationServiceContext(OrgService);
}
return _orgContext;
}
}

}
[/sourcecode]

Viel Spaß beim Probieren!

Microsoft Loopbackadapter unter Windows 7 64bit installieren

Der Loopbackadapter wird sehr häufig verwendet, wenn man eine Virtualisierungssoftware wie Virtual PC oder VMWare verwendet. Während VMWare die Treiber gleich automatisch installiert, ist das bei Virtual PC (leider?) nicht der Fall.

Geht dafür zunächst in den Geräte-Manager. Ihr findet ihn wie folgt:

  1. Start => Rechtsklick auf „Computer“ (im grau/schwarzen Bereich) => Eigenschaften
  2. Im aufgehenden Fenster findet Ihr links die Option „Geräte-Manager“ (blaugrauer Bereich)

Um nun den Adapter hinzuzufügen, selektiert zunächst in der Liste der Geräte den Knoten „Netzwerkadapter“. Im Hauptmenü (oben) klickt auf „Aktion“ und „Legacyhardware hinzufügen“.
Klickt auf „weiter“ und wählt „Hardware manuell aus einer Liste wählen und installieren (für fortgeschrittene Benutzer)“.

In der nun dargestellten Liste wählt nochmals „Netzwerkadapter“ aus und klickt auf weiter. Nach kurzem Warten könnt ihr in der Liste der Hersteller „Microsoft“ auswählen. Ist das passiert, scheint in der rechten Liste an 3 oder 4. Stelle der „Microsoft Loopbackadapter“ auf.

Nach Klick auf weiter, wird der Treiber installiert und der Adapter hinzugefügt.

Viel Spaß beim Testen!

Virtual PC auf Windows 7 installieren | IE7 auf Windows 7 testen

Wer kennt das nicht. Man schreibt eine Webanwendung, welche in allen gängigen Browsern 1a aussieht. Dann meldet sich der Kunde, dass in einer bestimmten, uralten Version des IE7 ein Darstellungsfehler auftritt. Was tun?

Nachdem IE7 unter Windows 7 nicht installierbar ist, muss eine virtuelle Maschine her.

Microsoft bietet Besitzern der Windows 7 Ultimate Version den sog. XP-Mode an. Das hat mir leider nicht weitergeholfen, da ich nur Enterprise in Verwendung habe. Allerdings bietet Microsoft direkt ein Image an, welches Windows 7 und IE7 enthält.

http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=11575

Auf dieser Seite lassen sich noch weitere Images finden, wie z.B. Windows XP mit IE6 oder Windows 7 mit IE8.

Unter nachfolgendem Link könnt Ihr Virtual PC 2007 kostenlos herunterladen. ACHTUNG: Bitte auf die Systemarchitektur achten, denn die 32-Bit Version lässt sich auf einem 64-Bit-System nicht installieren.

http://www.microsoft.com/downloads/de-de/details.aspx?displaylang=de&FamilyID=04D26402-3199-48A3-AFA2-2DC0B40A73B6

Nach der Installation und dem Hinzufügen des heruntergeladenen Images, stellt sich das nächste Problem. Wie kommunizieren die beiden (Host & Client) miteinander?

Hier gibt es nun mehrere Varianten. Um diese zu konfigurieren, geht man in die Einstellungen der virtuellen Maschine unter den Punkt „Netzwerk“.
Neben der Anzahl der Netzwerkadapter, ist hier primär die Einstellung bei Adapter 1 wichtig.

  •  Nicht verbunden – selbsterklärend, kein Netzwerkadapter wird am Client hinzugefügt.
  • Nur Lokal – Netzwerkadapter wird hinzugefügt, jedoch nicht verbunden
  • „Name d. Netzwerkkarte“ – Netzwerkadapter wird am Client hinzugefügt, am Host wird die ausgewählte Netzwerkkarte zur Kommunikation verwendet. ACHTUNG: Solltet Ihr mit einem Netzwerk verbunden sein, könnte das zu Problemen führen, da die TCP/IP-Einstellungen verändert werden!!
  • Microsoft Loopbackadapter – Netzwerkadapter wird am Client hinzugefügt, am Host wird die virtuelle Netzwerkkarte zur Kommunikation verwendet. Falls diese Eigenschaft nicht bei euch auftaucht, lest bitte folgenden Blog-Eintrag.
  • Gemeinsames Netzwerk (NAT) – Netzwerkadapter wird am Client hinzugefügt, der Client versucht, die gleichen Netzwerkeinstellungen zu beziehen wie die prim. Netzwerkkarte am Host.

Nachdem bei uns im Firmennetzwerk durch div. Restriktionen die Option „Gemeinsames Netzwerk (NAT)“ wegfällt, musste ich zunächst den Loopbackadapter installieren. Wie das funktioniert, erfährt Ihr im nachfolgenden Blog-Eintrag. Loopbackadapter unter Windows 7 installieren.
Viel Spaß beim Testen!

IIS7: ApplicationPool auf 32bit umstellen

Wenn Ihr eine Webanwendung habt, welche Ihr z.B. noch unter Windows XP entwickelt habt oder welche 3rd-Party-Komponenten verwendet, die nicht 64-bit-tauglich sind, so bekommt Ihr auf einem 64-Bit-System folgende Fehlermeldung:

Die Datei oder Assembly „DirectShowLib“ oder eine Abhängigkeit davon wurde nicht gefunden. Es wurde versucht, eine Datei mit einem falschen Format zu laden.

Das Problem lässt sich einfach lösen, in dem man den Application Pool konfiguriert. Geht dafür in den IIS-Manager, Anwendungspools und selektiert den Pool eurer Anwendung. Klickt dann im rechten Menü auf „Erweiterte Einstellungen…“.

Im sich öffnenden Fenster findet Ihr die Einstellung „32-bit-Anwendungen aktivieren“ und setzt dieses auf „True“.

Danach sollte eure Anwendung wie erwartet funktionieren!

Viel Spaß!

Javascript als Resource verpacken/entpacken (Arbeiten mit ScriptReferences/ScriptManager)

Manche von euch haben vielleicht meinen letzten Blog-Entry
Javascript als Resource verpacken/entpacken (Arbeiten mit Embedded Resources) gelesen. Mein Mitstreiter domiwaldi hat mich auf die Idee gebracht, statt dem ClientScriptManager den ScriptManager zu verwenden. Nachdem das Vorteile bei u.a. der Komprimierung hat, habe ich mir das mal angesehen und siehe da, der Quellcode liess sich auch gleich um einiges Verkürzen.

Nachfolgend seht ihr die aktualisierte Version der RegisterBasicLib-Funktion:

[sourcecode language=“csharp“]
public const string FnJquery = "Utils.Web.Javascript.BasicLib.Resources.jquery-1.4.1.js";
public const string FnJqueryUi = "Utils.Web.Javascript.BasicLib.Resources.jquery-ui-1.8.5.custom.min.js";
public const string FnCookie = "Utils.Web.Javascript.BasicLib.Resources.jquery.cookie.min.js";
public const string FnJson = "Utils.Web.Javascript.BasicLib.Resources.jquery.json-2.2.min.js";
public const string FnBasicLib = "Utils.Web.Javascript.BasicLib.Resources.Utils.BasicLib.js";

/// <summary>
/// Registers the basic lib.
/// </summary>
/// <param name="p">The p.</param>
public static void RegisterBasicLib(ScriptManager sm)
{
Page p;
if (IsBasicLibEnabled)
{
ScriptReference sr = new ScriptReference(FnJquery, System.Reflection.Assembly.GetExecutingAssembly().FullName);
sm.Scripts.Add(sr);

sr = new ScriptReference(FnJqueryUi, System.Reflection.Assembly.GetExecutingAssembly().FullName);
sm.Scripts.Add(sr);

sr = new ScriptReference(FnJson, System.Reflection.Assembly.GetExecutingAssembly().FullName);
sm.Scripts.Add(sr);

sr = new ScriptReference(FnCookie, System.Reflection.Assembly.GetExecutingAssembly().FullName);
sm.Scripts.Add(sr);

sr = new ScriptReference(FnBasicLib, System.Reflection.Assembly.GetExecutingAssembly().FullName);
sm.Scripts.Add(sr);

}
}
[/sourcecode]

Wichtig ist jedoch zu bedenken, dass die Resourcen als Webresourcen in der Assembly.info deklariert werden müssen.

[sourcecode language=“csharp“]
[assembly: System.Web.UI.WebResource(Utils.Web.BasicLib.FnBasicLib, "text/javascript")]
[assembly: System.Web.UI.WebResource(Utils.Web.BasicLib.FnCookie, "text/javascript")]
[assembly: System.Web.UI.WebResource(Utils.Web.BasicLib.FnJquery, "text/javascript")]
[assembly: System.Web.UI.WebResource(Utils.Web.BasicLib.FnJqueryUi, "text/javascript")]
[assembly: System.Web.UI.WebResource(Utils.Web.BasicLib.FnJson, "text/javascript")]
[/sourcecode]

Die Markierung als Embedded Resource im Properties-Dialog ist hingegen nicht mehr notwendig. Auch das vorherige entpacken und abspeichern der Datei ist nicht länger notwendig, was die Geschichte sehr angenehm macht.

Viel Spaß !