Erstmal vielen Dank an HLMC für das Ausrichten der SharePoint Days. Das Konferenz Center hat super zu der Veranstaltung gepasst!
Auch vielen Dank an die Audience die in meinem Vortrag zum Thema SharePoint meets JavaScript – Schnelle und leichtgewichtige Lösungen per Script war. Ich hatte sehr viel Spaß!

Die Slides habe ich auf Speakerdeck hochgeladen.
https://speakerdeck.com/daniellindemann/sharepoint-meets-javascript-v2

Der Quellcode von den Demos liegt auf GitHub im meinem Code Samples Repository.
https://github.com/daniellindemann/CodeSamples

Der Code kann auch direkt Online angeschaut werden unter https://github.com/daniellindemann/CodeSamples/tree/master/Conferences/2015_SharePointDays/SharePoint%20meets%20JavaScript

Dieses Jahr durfte ich wieder als Speaker auf der Basta sein. Es ist mir immer wieder eine Freude an dem Event teil zu nehmen!
Vielen Dank an die Audience in meinem Vortrag SharePoint meets JavaScript – Schnelle und leichtgewichtige Lösungen per Script. Es hat mir sehr viel Spaß gemacht!

Die Folien dazu gibt es auf Speakerdeck unter https://speakerdeck.com/daniellindemann/sharepoint-meets-javascript .

Die Demos habe ich in mein Code Samples GitHub Repository gepusht.
https://github.com/daniellindemann/CodeSamples

Online einsehbar unter https://github.com/daniellindemann/CodeSamples/tree/master/Conferences/2015_Basta/SharePoint%20meets%20JavaScript

Vielleicht darf ich nächstes Jahr wieder nach Mainz kommen. 🙂

Schön und aufregend war die Basta 2014.
Vielen Dank an die Zuhörer in meiner Session Authorize your Apps! – OAuth in SharePoint 2013 und natürlich auch an die Zuhörer die zu später Stunde noch in der Session Die Zukunft von SharePoint und Office 365 von Fabian Moritz waren, in der ich Demos zur neuen Office 365 API gezeigt habe.

Die Slides zu meiner Session gibt’s auf Speaker Deck und die Demos habe ich in GitHub bereitgestellt.
Viel Spaß damit! 😀

Authorize your Apps! – Oauth in SharePoint 2013

Demos

Die Zukunft von SharePoint und Office 365

Demos

Ich musste heute feststellen, dass das Öffnen von SiteCollections über die Site ID nicht immer die beste Lösung ist. Natürlich ist es die schnellste, das bleibt mir als Entwickler erstmal im Gedächtnis. Nur manchmal ist es nicht möglich oder sinnvoll eine SiteCollection über die ID zu öffnen.
Doch erstmal zum Problem…

Bei mir trat ein Problem beim Arbeiten mit Bilder und Links auf. Links haben auf eine falsche URL gezeigt und Bilder wurden nicht geladen, auch wegen einer falschen URL.
Den Bösewicht, der das Problem verursacht hat, habe ich schnell gefunden. Das Portal hat mehrere AAM’s (Alternate Access Mappings) zum Zugriff auf die SharePoint-Seite zu Verfügung gestellt. Eine URL für den internen Zugriff und eine URL für den öffentlichen Zugriff.

Auf dem Portal gibt es eine „Über“-SiteCollection, die den Einstiegspunkt auf die Website markiert. Diese SiteCollection enthält Listen mit allgemeinen Daten die auch in Sub-SiteCollection verfügbar sein müssen. Dahinter können beliebige Teamsites liegen. Alle Teamsites brauchen Infos aus der „Über“-SiteCollection. Damit dieser Zugriff einfacher wird habe ich in dem Projekte einen Wrapper gebaut, der einen Context zu der „Über“-SiteCollection aufbaut und diese öffnet. Und wie schon oben erwähnt natürlich über IDs.

Portalstruktur visualisiert

Portalstruktur visualisiert


public class UeberContext : IDisposable
{
	private readonly SPContext _spContext;
	private SPSite _uSite;
	private SPWeb _uWeb;

	private UeberContext(SPContext spContext)
	{
		_spContext = spContext;
	}

	private static UeberContext Instance
	{
		get
		{
			var ueberContext = HttpContext.Current.Items["UeberSiteInstance"] as UeberContext;
			if (ueberContext == null)
			{
				var spContext = SPContext.Current;
				var instance = new UeberContext(spContext);
				HttpContext.Current.Items["UeberSiteInstance"] = instance;
				return instance;
			}
			return ueberContext;
		}
	}

	public static UeberContext Current
	{
		get { return Instance; }
	}

	public SPSite Site
	{
		get
		{
			var site = _spContext.Site;
			var rootWeb = site.RootWeb;
			if (IsMasterSite(rootWeb))
				return site;
			else
			{
				var siteID = new Guid(rootWeb.Properties["UeberSiteID"]);
				_uSite = new SPSite(siteID);
				return _uSite;
			}
		}
	}

	public SPWeb Web
	{
		get
		{
			var site = _spContext.Site;
			var rootWeb = site.RootWeb;
			if (IsMasterSite(rootWeb))
				return rootWeb;
			else
			{
				var webID = new Guid(rootWeb.Properties["UeberWebID"]);
				_uWeb = Site.OpenWeb(webID);
				return _uWeb;
			}
		}
	}

	private bool IsMasterSite(SPWeb supposedWeb)
	{
		// Check if the supposedWeb is already the rootweb of the Über sitecollection
		// simplified it's always true for demo code
		var _isMasterSite = true;
		return _isMasterSite;
	}

	public void Dispose()
	{
		if (_uWeb != null)
			_uWeb.Dispose();
		if (_uSite != null)
			_uSite.Dispose();
	}
}

Wie aus dem Code hervorgeht, speichere ich die IDs zur „Über“-SiteCollection im PropertyBag des RootWebs der aktuell besuchten SiteCollection. Ist die aktuelle SiteCollection schon die „Über“-SiteCollection, gebe ich das SPWeb-Objekt und das SPSite-Objekt des SharePoint-Context’s zurück. Wenn nicht wird die „Über“-SiteCollection geöffnet.

Nun wieder zurück zu dem Problem mit den multiplen AAM’s…

Da die „Über“-SiteCollection über die ID geöffnet wird, gibt diese immer den Context des Default AAM zurück. Das bedeutet es wird der Default AAM benutzt obwohl die Seite über den Internet AAM besucht wird.

Ein Beispiel:
Ich habe 2 AAM’s in meiner WebApplication konfiguriert .Einen Default und einen für Internet.
    Default = http://intern.dlindemann.de
    Internet = http://extern.dlindemann.de

Besucht wird eine Sub-SiteCollection über den Internet AAM http://extern.dlindemann.de/meine-subsite. Auf dieser Seite befindet sich ein WebPart, der Daten aus einer Liste in der „Über“-SiteCollection unter http://extern.dlindemann.de abrufen soll. Die IDs zur „Über“-SiteCollection stehen im PropertyBag der Sub-SiteCollection und ich kann mit oben beschriebenem UeberContext auf die „Über“-SiteCollection zugreifen (Variable ueberWeb).

var ueberWeb = UeberContext.Current.Web;
SPList list = ueberWeb.GetList("/lists/einfacheListe");
// ... do something with the list

Das funktioniert soweit ganz gut.
Doch nun enthält diese Liste ein URL-Feld zum verlinken von Bildern, die ich gerne mit dem WebPart ausgeben möchte. Egal ob das URL Feld mit einer vollen URL oder einer server-relativen URL befüllt wurde, SharePoint speichert diese Bilder-URLs immer server-relativ ab, wenn sie innerhalb derselben SharePoint-WebApplication liegen. Externe Bilder werden natürlich mit vollem Pfad gespeichert.

Liest man nun ein Bild aus, wird die URL von SharePoint zu einer vollwertigen URL umgewandelt und das im Context des geöffneten SPSite-Objekts.
Mein Bild mit der URL /SiteCollecionImages/mein-bild.png wird zu http://intern.dlindemann.de/SiteCollectionImages/mein-Bild.png obwohl ich die Website über die URL http://extern.dlindemann.de besuche. Denn mein UeberContext der die SiteCollection mittels ID öffnet gibt mir die URL des Default AAM’s zurück.
Die Folge davon ist, dass das Bild nicht geladen werden kann (HTTP Status 502 Bad Gateway).

var imageItem = list.GetItemById(1);
var imageItemField = item.Fields.GetFieldByInternalName("MyImage");
string fieldValue = item[imageItemField.Id].ToString();
var imageFieldValue = new SPFieldUrlValue(fieldValue);
this.Controls.Add(new Image() { ImageUrl = imageFieldValue.Url });

Die Lösung um das Problem zu beheben ist die URL des aktuellen Context (hier http://extern.dlindemann.de/meine-subsite) zu benutzen, was viel URL zerschneiden mit sich bringt. Oder man benutzt die Methode RebaseUriWithAlternateUri um an die URL des richtigen AAM’s heranzukommen statt die ID zu benutzen. Benutzt man die URL um das SPSite-Objekt zu öffnen dauert es ein paar Millisekunden länger. Doch dafür hat Context garantiert die richtige URL. Benutzt man die Methode RebaseUriWithAlternateUri dauert es nochmal etwas länger und man muss auf die Farm zugreifen, deshalb sollte man bei dieser Methode den AAM Zwischenspeichern.

public SPSite Site
{
	get
	{
		var site = _spContext.Site;
		var rootWeb = site.RootWeb;
		if (IsMasterSite(rootWeb))
			return _spContext.Site;
		else
		{
			var ueberSiteUrl = rootWeb.Properties["UeberSiteUrl"].ToString();

			// getting current aam        
			var rootWebUri = new Uri(rootWeb.Url, UriKind.Absolute);
			var webAppUri = new Uri(rootWebUri.GetLeftPart(UriPartial.Authority));
			SPAlternateUrl currentAam = null;
			SPSecurity.RunWithElevatedPrivileges(() =>
			{
				currentAam = SPFarm.Local.AlternateUrlCollections.LookupAlternateUrl(webAppUri);
			});

			// get url for site    
			var ueberSiteUri = SPFarm.Local.AlternateUrlCollections.RebaseUriWithAlternateUri(new Uri(ueberSiteUrl), currentAam.UrlZone);
			_uSite = new SPSite(ueberSiteUri.ToString());

			// open site with correct url    
			return _uSite;
		}
	}
}

Durch diesen Code wird aus der URL http://intern.dlindemann.de die in dem PropertyBag unter UeberSiteUrl gespeichert wurde automatisch http://extern.dlindemann.de wenn die Seite über den Internet AAM besucht wird.

Ich weiß, der Code gewinnt keinen Schönheitswettbewerb 🙂 . Das liegt daran, dass alles stark vereinfacht ist und kein Caching benutzt wird. Ich hoffe ich kann damit trotzdem die Problematik von URLs und verschiedenen AMM’s aufzeigen.

Happy Coding!