Suchprovider

Die in cardo integrierte Suche (das immer präsente Textfeld - Schnellstart) verwendet intern eine Reihe von sogenannten "Suchprovidern". Dabei gibt es vom System her fest integrierte Suchprovider, wie z.B. die Suche nach Ebenentiteln.

Die Suche wird dabei bereits nach n-Tastendrücken ausgeführt und liefert eine Ergebnisliste, die beim Weitertippen verfeinert werden kann.

Dies bedeutet, dass die Suchprovider sehr oft aufgerufen werden und entsprechend kurze Antwortzeiten, wir empfehlen als Richtwert max. 100ms, bieten müssen.

Der folgende Artikel stellt kurz unsere Ideen für die Schnittstellenarchitektur vor und beschreibt beispielhaft die Implementierung eines eigenen Suchproviders .

Umsetzung

Ein Suchprovider ist ein instanzierbarer Typ (Klasse), der das Interface IduIT.cardo.Core.Api.Search.ISearchProvider implementiert und über einen parameterlosen Konstruktor verfügt.

D.h. das System ermittelt alle in der aktuellen Installation verfügbaren Suchprovider anhand der konkreten Typen, welche das o.g. Interface implementieren. Noch konkreter: Eine dll mit der Implementierung muss im bin-Ordner der Webanwendung vorhanden sein.

Folgende Schnittstellen / Basisklassen sind im Namespace IduIT.cardo.Core.Api.Search für Implementierungen vorgesehen:

Ein Suchprovider sollte nie IDisposable implementieren (müssen).

Vorüberlegungen

Was wird gesucht?

So wie es die Benutzer heutzutage gewohnt sind: Ein beliebiger Text, das System soll irgendwie das "Beste Ergebnis" dazu bieten.

D.h. die Schnittstellen agieren mit einem einfachem String-Input.

Was wird gefunden?

Damit das System den Treffer verarbeiten kann, muss jedes Ergebnis die Instanz eines Wellknown-Type sein. Dabei stehen die systemdefinierten Typen oder eigene Implementierungen zur Verfügung.

Sofern passend, sollten Sie einen der systemdefinierten Typen verwenden. Diese finden Sie alle im Namespace IduIT.cardo.Core.WellKnownType

Die möglichen Aktionen mit dem Suchtreffer werden dabei von der Umgebung erkannt und dem Nutzer angeboten.

Sicherheit?

Nicht jeder Nutzer darf alles sehen. Nachdem alle Suchprovider Ergebnisse geliefert haben, wird vor der Auslieferung der Ergebnisse für jeden Eintrag geprüft, ob der Ergebnis (=WellknownType) das Zusatzinterface ISecurityCriticalSearchContent implementiert und dann ggf. nachträglich ausgefiltert.

Gibt es das bestmögliche Ergebnise?

Dann sollte das System mit der Suche aufhören und sofort diesen Treffer liefern. Dieses Verhalten kann mittels SearchMatchBehavior gesteuert werden.

Anleitung zur Implementierung

IduIT.cardo.Core.Api.Search.ISearchProvider

Diese Schnittstelle ist recht einfach, die folgende Beispielimplementierung ist voll funktionsfähig.

Durch die Wahl des Rückgabetyps WellknownType.Feature stehen z.B. die Aktionen "Ebene in Karte anzeigen", "Ebene im Themenbaum anzeigen", "Export Esri Shapefile" zur Nutzung des Treffers zur Verfügung.

Diese Implementierung prüft ob genau 2 Suchworte eingegeben wurden, eines davon muss "IDU" sein, dann wird anhand des 2. Wortes der Standort ausgewählt.

/// <summary>
/// Konkrete Klasse, diesen muss über einen parameterlose 
/// CTOR verfügen. Die Klasse muss nicht public sein.
/// </summary>
internal sealed class IduITLocationProvider : IduIT.cardo.Core.Api.Search.ISearchProvider
{
	/// <summary>
	/// Anzeigetitel (wird derzeit nicht verwendet)
	/// </summary>
	public string InitOnlyTitle
	{
		get
		{
			return "Demo-Suche";
		}
	}
	/// <summary>
	/// Beschreibung (kein html,wird derzeit nicht verwendet))
	/// </summary>
	public string InitOnlyDescription
	{
		get
		{
			return "Zeigt den Standort der IDU IT auf der Karte an";
		}
	}
	/// <summary>
	/// Wenn *alle* Instanzmethoden thread-sicher verwendet werden können. 
	/// dann pro cardo Instanz nur eine Instanz des Suchproviders erstellen.
	/// Sonst pro Anfrage eine eigene Instanz.
	/// </summary>
	public bool IsReusable
	{
		get
		{
			return true;
		}
	}
	/// <summary>
	/// Wenn dieser Suchprovider mind. 1 Treffer liefert
	/// dann die bisher ermittelten Treffer sofort zurückgeben 
	/// und keine weitern Provider mehr fragen,
	/// oder SearchMatchBehavior.Default ... weitermachen.
	/// </summary>
	public SearchMatchBehavior MatchBehavior
	{
		get
		{
			return SearchMatchBehavior.StopDefaultSearchesIfNotEmpty;
		}
	}
	/// <summary>
	/// Ist dieser Provider in der aktuellen Installation sichtbar?
	/// Meist: true
	/// </summary>
	/// <returns></returns>
	public bool InitOnlyIsVisibleInThisInstallation()
	{
		return true;
	}
	/// <summary>
	/// Führt die Suche aus.
	/// </summary>
	/// <param name="term"></param>
	/// <returns></returns>
	public IEnumerable<SearchHit> Search(SearchTerm term)
	{
		//verwende die geplitten Suchworte (immer lower-case)...
		var words = term.Words;

		//macht es sinn, mit der Eingabe hier zu suchen ...?
		if (!(words.Count != 2 || !words.Contains("idu")))
		{
			if (words.Contains("dresden"))
				yield return new SearchHit(
					new IduIT.cardo.Core.WellKnownType.Feature(
						IduIT.GeoLib.Net.GeometryFactory.CreatePointGeometry(
							13.759, 51.107, 4326),
							"Standard IDU in Dresden"),
						1000/*score*/);
			else if (words.Contains("zittau"))
				yield return new SearchHit(
					new IduIT.cardo.Core.WellKnownType.Feature(
						IduIT.GeoLib.Net.GeometryFactory.CreatePointGeometry(
							14.812, 50.900, 4326),
							"Standard IDU in Zittau"),
						1000/*score*/);
		}
	}
}
Ergänzende Hinweise

Innerhalb der Klasse steht wie üblich der Zugriff auf das cardo System über Cardo4.Env zur Verfügung.

Bspw.:

Cardo4.Env.CurrentUser.DisplayName

Der Request wird immer innerhalb eines Web-Requests im Haupthread durchgeführt, so dass auch Zugriff auf System.Web.HttpContext... etc. möglich sind.

Eine Task-basierte asynchrone Ausführung ist für einen späteren Zeitpunkt angedacht. Dazu wird zu gegebener Zeit ein neues Interface bereitgestellt.

Einstellungen für Suchprovider

Sollte der Suchprovider Einstellungen benötigen, dann ist der empfohlene Weg, diese über das System konfigurierbar vorzunehmen, d.h. nicht über web.config oder ähnliche Konstrukte.

Dazu stellt cardo4 das Konzept von Anwendungen zur Verfügung. Anwendungen werden immer von der Laufzeitumgebung instanziert und können nicht selber erstellt werden. Damit ist sichergestellt, dass sämtliche Einstellungen korrekt belegt werden.

Um aus einem Suchprovider Zugriff auf eine Anwendung zu erhalten, kann von der Vorlagen-Basisklasse SearchProviderWithApplicationBase abgeleitet werden.

IduIT.cardo.Core.Api.Search.SearchProviderWithApplicationBase

Diese abstrakte Klasse erfordert zwei Vorlagenargumente. Den Typ der Anwendungsklasse und den Typ der Einstellungsklasse.

Implementierungen für SearchProviderWithApplicationBase sollten immer für IsReusable true zurückgeben (und sich entsprechend auch daran halten). Das Abrufen der Einstellungen kann damit auf einen Aufruf für die Lebenszeit der Anwendung reduziert werden.

Änderungen der Einstellungen werden automatisch an die Instanz übermittelt.

Diese Anwendung kann auch als "richtige" Anwendung verwendet werden und nur zusätzlich den Suchprovider bereitstellen oder nur als Einstellungscontainer für den Suchprovider umgesetzt werden, wie im folgenden Beispiel.

IduIT.cardo.Core.Api.Search.ISearchProviderWithFeatureResult

Diese Schnittstelle definiert Suchprovider, die als Treffer garantiert Typ WellknownType.Feature (Geometrie) zurückliefern.

Alle Suchprovider dieses Typs werden auch in der Geometrie-Toolbox als Datenquelle mit angeboten.

Umsetzung 1/3

Wie der Name bereits sagt, muss für ICardoManagedApplicationWithSystemSettings eine Einstellungsklasse angegeben werden.

Diese muss zuerst bereitgestellt werden.

Die Klasse kann auch komplexer sein und weitere Unterklassen enthalten, z.B. pro Such-Typ o.ä.

/// <summary>
/// Einstellungsklasse für Suchprovider ...
/// </summary>
[IduIT.Core.PropertyModel.PropertyModelClass]
public sealed class SearchProviderSettings : CardoManagedApplicationSettings
{
	/// <summary>
	/// Eine Bsp. Einstellung
	/// </summary>
	[PropertyModelProperty(Title = "Beispiel-Wert")]
	[Newtonsoft.Json.JsonProperty("sampleValue")]
	public string SampleValue
	{
		get;
		set;
	}
	/// <summary>
	/// Prüfung, Änderungen werden nur übernommen, wenn keine Fehler vorliegen.
	/// </summary>
	/// <returns></returns>
	public override ValidationResult Validate()
	{
		var res = new ValidationResult();
		if (String.IsNullOrEmpty(SampleValue))
			res.AddMessage(() => SampleValue, "Bitte geben Sie irgendetwas ein ...");
		return res;
	}
}

Die Einstellungsklasse sollte unbedingt statuslos sein und keine zu disposenden Member beeinhalten.

Umsetzung 2/3

Die Anwendung für dieses Bsp. dient nur als Container für die Einstellungen, d.h. wir haben keine Oberfläche, Script-Registrierungen o.ä.

/// <summary>
/// Bsp. Anwendung, nur als Container für die 
/// Suchprovider-Einstellungen
/// </summary>
public sealed class SearchProviderApplication
: ICardoManagedApplicationWithSystemSettings<SearchProviderSettings>
{
	public SearchProviderSettings Settings { get; set } = new SearchProviderSettings();

	public string InitOnlyTitle => "Bsp-Suchprovider";

	public string InitOnlyDescription => String.Empty;

	public void Dispose() { }

	public bool InitOnlyAllowMultipleRegistration() { return false; }

	public bool InitOnlyIsVisibleInThisInstallation() { return true; }

	public void OnLoadRegisteredApplication(string registeredInstanceId) { }

	public ValidationResult ValidateGivenSettings(SearchProviderSettings settings)
	{
		return settings.Validate();
	}
}

Damit ist die Anwendung fertig und kann im System registriert werden.

Beachten Sie: Die Start-Berechtigung der Anwendung wird für den Suchprovider nicht beachtet. Wir gehen nicht davon aus, dass Suchprovider allgemein über Rechte eingeschränkt werden sollten. Für jeden Instanz des Providers müssten sonst immer die Nutzer-Berechtigungen geprüft werden.

Umsetzung 3/3

An dieser Stelle sind alle Vorbereitungen getroffen.

Der Suchprovider aus dem Anfang des Artikels kann in folgender Form erweitert werden (hier dargestellt sind die angepassten Methoden):

/// <summary>
/// Konkrete Klasse, dies muss über einen parameterlose 
/// CTOR verfügen. Die Klasse muss nicht public sein.
/// </summary>
internal sealed class IduITLocationProvider :
	//Neu => Ableitung von 
	IduIT.cardo.Core.Api.Search.SearchProviderWithApplicationSettingsBase<SearchProviderApplication, SearchProviderSettings>,
	//wie gehabt ... ISearchProvider
	IduIT.cardo.Core.Api.Search.ISearchProvider
{
		/// <summary>
		/// Änderung: override, die Basisklasse definiert diese Eigenschaft abstract
		/// </summary>
		public override bool IsReusable
		{
			get
			{
				return true;
			}
		}
	    /// <summary>
	    /// Ist dieser Provider in der aktuellen Installation sichtbar?
	    /// Hier kann auf Settings geprüft werden.
	    /// </summary>
	    /// <returns></returns>
	    public bool InitOnlyIsVisibleInThisInstallation()
	    {
		    return Settings != null;
	    }

		/// <summary>
		/// Änderung: Suche nur, wenn Settings != null, Zugriff auf die Einstellung ...
		/// </summary>
		public IEnumerable<SearchHit> Search(SearchTerm term)
		{
			//die Basis-Klasse bringt eine Eigenschaft "Settings", vom Typ SearchProviderSettings
			//wenn keine Einstellungen vorhanden sind, die Anwendung nicht registriert ist etc.
			//sind diese null

			//=> Bei Änderungen werden die Settings automatisch neu gesetzt
			//das Zuweisen über eine Zwischenvariable wird empfohlen!
			var appSettings = Settings;
			if (appSettings != null)
			{
				var sample = appSettings.SampleValue;
				//die Suche wieder wie oben, Auswerten der Argumente hier etc ....
			}
		}
}

Die Settings-Eigenschaft ist vom Typ des Vorlagenarguments für die Einstellungen. Änderungen in den Anwendungseinstellungen werden an die laufende Instanz mit übermittelt, aber nur sofern das Validieren der Einstellungsklasse keine Fehler ergab.

Beachten Sie, dass SearchProviderWithApplicationSettingsBase selber nicht ISearchProvider implementiert.


Zuletzt geändert: 04.03.2018 17:44:36 (erstmals erstellt 04.03.2018)