Hallo miteinander,
mir ist leider keine bessere Überschrift eingefallen; Daher kann diese gerne im Laufe des Gesprächs durch einen Mod. angepasst werden.
Folgende Bibliothekenstruktur habe ich aktuell umgesetzt.
FooApp.Common
FooApp.UI -> benutzt intern ua. AOI.RemoteControl
FooApp.RemoteControl -> TCP ClientServer Bibliothek
FooApp.Server.RemoteControl -> Schnittstellen Server zwischen SPS und FoOApp.UI. Benutzt ua. FooApp.RemoteControl / OPC.FooApp
fertige Anwendung die als Standartprodukt vertrieben wird.
OPC.Alarms -> Schnittstelle zwischen SPS und Suite.Common
OPC.FooApp -> Schnittstelle zwischen SPS und FooApp.Common
OPC.Local -> OPC Client
OPC.AOI -> Schnittstelle zwischen SPS und AOI.Common
OPC.QuellTextGenerator -> Software um SPS auszulesen und daraus Quellen etc. zu generieren
OPC.Leitsystem -> Schnittstelle zwischen SPS und Suite.Common
Suite.Common -> Sämtliche Interfaces und Basisklassen
Suite.ErrorCodes -> Unternehmensweite Deklaration von Fehlercodes. Wird in jeder Anwendung verwendet
Suite.GraphQL.Client -> Benutzt Suite.Common um mit dem Suite.Server zu kommunizieren
Suite.GraphQL.Server -> Benutzt Suite.Common um als Suite.Server zu fungieren und mit dem GraphQL.Client zu kommunizieren
Suite.Local -> Beinhaltet sämtliche Module wie bspw. Alarme, Datenbanken, Benutzerverwaltung, Betriebsdaten, Übersetzungen, Leitsystem
Suite.Server -> Serveranwendung. Benutzt ua. OPC.Alarms, OPC.Leitsystem, OPC.Local, sowie projektabhängig AOI.RemoteControl und/oder FooApp.RemoteControl
Suite.UI.Leitstand -> Anwendung zur Verwaltung des Leitsystems. Benutzt intern den Suite.GraphQL.Client
Suite.UI.Client -> Geführter Werkeranwendung. Benutzt intern den Suite.GraphQL.Client
Aus dem Suite "Softwarepaket" wird für unseren Sonderanlagenbau das Kundenprojekt entwickelt.
AOI.RemoteControl -> TCP Client zur Kommunikation mit einem AOI
AOI.Common -> Schnittstellen
AOI.Server.RemoteControl -> Schnittstellen Server zwischen SPS und AOI. Benutzt ua. AOI.RemoteControl / OPC.AOI
AOI.UI.RemoteControl -> Windows Anwendung zum Fernbedienen des AOI. Benutzt ua. AOI.RemoteControl
Mit der folgenden Struktur bin ich aktuell sehr zufrieden.
Eine spezifische Kundenanwendung lässt sich schnell umsetzen durch das hinzufügen der entsprechenden Bibliotheken. Über die DI werden die Interfaces (*.Common) entsprechend zugewiesen. Sei es nun eine Server oder Clientanwendung.
Als nächsten Schritt würde ich die *.RemoteControl Klassen in ein eigenes Projekt auslagern wollen.
Remote.Common -> Beinhaltet Interfaces für FooApp, AOI usw.
Remote.TCP -> Basis TCP Client
Remote.Serial -> Basis SerialCommunication Client
Remote.AOI -> AOI Client / Server
Remote.FooApp -> FooApp Client / Server
In dem FooApp und dem AOI Projekt müsste ich nur noch die entsprechende Remote.##AppName## Bibliothek einbinden, passendes Protokoll TCP / Serial oder beides hinzufügen und hätte in allen Anwendung den selben Client / Server.
Dabei stellt sich nun die Frage ob es nicht sinnvoller ist, die "Teilprojekte" in die Suite zu packen, da dort in der Regel die komplette Logik vollzogen wird und Debuggen dadurch erheblich einfacher wäre. Konkret meine ich, folgende Projekte
OPC.Alarms
OPC.Leitsystem
werden in das Suite Projekt verschoben:
Suite.OPC -> Beinhaltet OPC.Local bzw. die komplette Logik aus ehemals OPC.Alarms / OPC.Leitsystem
Dadurch würde bei einer Anpassung unseres Suite Projektes (bzw. deren Interfaces) kein erneutes Anpassen der OPC Bibliothek erforderlich. Wenn sich jedoch etwas grundlegendes an der OPC Kommunikation ändert, so müsste man das OPC Projekt aktualisieren und (ggfs.) nur im Kundenprojekt auf die letzte OPC Version gehen.
Dies würde natürlich ebenso für den AOI Teil und dann auch die noch nicht implementierte Remote.* Bibliothek erfolgen.
Ich hoffe mein Problem kann der eine oder andere nachvollziehen und mir einen guten Rat geben wohin ich mit meiner Reise gehen soll bzw. wie man bei so einem Fall am besten seine Projekte aufteilt.
Vielen Dank Vorab!
Gruß
Dominic
Habe eine Erläuterung gefunden: https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
The message is rather cryptic, but the meaning is actually quite simple: WPF doesn’t know which FrameworkElement to use to get the DataContext, because the column doesn’t belong to the visual or logical tree of the DataGrid.
Ich glaube,
> wäre hier die einfachste Möglichkeit.
Das habe ich bereits getestet gehabt.
Thread kann geschlossen werden
Hallo zusammen,
Ich habe ein Control (ZlsUnitBrowserActive) mit zwei DependencyProperties MaxWidthIdentifier/PropertiesDataTemplate. Wenn ich versuche die beiden properties über "RelativeSource" anzubinden, bekomme ich immer folgende Fehlermeldung:
Fehlermeldung:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='PlcFramework.Zls.Production.ProductionControls.ZlsUnitBrowserActive', AncestorLevel='1''. BindingExpression:Path=PropertiesDataTemplate; DataItem=null; target element is 'DataGridTemplateColumn' (HashCode=30225241); target property is 'CellTemplate' (type 'DataTemplate')
<Style TargetType="productionControls:ZlsUnitBrowserActive">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="productionControls:ZlsUnitBrowserActive">
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate
MaxWidth="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=productionControls:ZlsUnitBrowserActive}, Path=MaxWidthIdentifier}"
CellTemplate="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=productionControls:ZlsUnitBrowserActive}, Path=PropertiesDataTemplate}"/>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
Nun habe ich bereits die Lösung hier gefunden: https://www.mycsharp.de/wbb2/thread.php?postid=3787237
<Style TargetType="productionControls:ZlsUnitBrowserActive">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="productionControls:ZlsUnitBrowserActive">
<DataGrid>
<DataGrid.Resources>
<xamlHelper:BindingProxy x:Key="DataContextBindingProxy" Data="{Binding RelativeSource={RelativeSource TemplatedParent}}"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate
MaxWidth="{Binding Path=Data.MaxWidthIdentifier, Source={StaticResource DataContextBindingProxy}}"
CellTemplate="{Binding Path=Data.MaxWidthIdentifier, Source={StaticResource PropertiesDataTemplate}"/>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
Kann mir bitte einer eine Erklärung geben warum ich das ganze so lösen muss oder gibt es vielleicht noch eine bessere bzw. elegantere Lösung?
Danke!
Habe meinen Post editiert..
Hallo zusammen,
ich habe das Problem das seit Neustem meine "MarkupExtensions" nicht mehr in VisualStudio sauber funktionieren. Dabei erhalte ich immer folgenden Fehler:
Fehlermeldung:
ArgumentException: "ThemeExtension" ist für "Setter.Value" nicht gültig. Unterstützt werden nur die MarkupExtension-Typen "DynamicResourceExtension" und "BindingBase" oder abgeleitete Typen.
bei System.Windows.Setter.Seal()
bei System.Windows.SetterBaseCollection.Seal()
bei System.Windows.Style.Seal()
bei System.Windows.StyleHelper.UpdateStyleCache(FrameworkElement fe, FrameworkContentElement fce, Style oldStyle, Style newStyle, Style& styleCache)
bei System.Windows.FrameworkElement.OnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
bei System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
bei System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
bei System.Windows.Controls.TextBox.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
bei System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args)
bei System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
bei System.Windows.DependencyObject.InvalidateProperty(DependencyProperty dp, Boolean preserveCurrentValue)
bei System.Windows.FrameworkElement.UpdateStyleProperty()
bei System.Windows.FrameworkElement.OnInitialized(EventArgs e)
bei System.Windows.FrameworkElement.TryFireInitialized()
bei System.Windows.FrameworkElement.EndInit()
bei MS.Internal.Xaml.Runtime.ClrObjectRuntime.InitializationGuard(XamlType xamlType, Object obj, Boolean begin)
Dies hat nun seid gut 6 Wochen ohne Probleme funktioniert und von heute auf Morgen spackt mein VS rum und ich kann nicht mehr arbeiten. Auch Blend schafft es nicht. Sobald ich den Code ausführe läuft das alles wie es soll. Ändere ich ein Zeichen in meinem XAML Code wird dieser auch fehlerfrei angezeigt und sauber kompilliert (design time).
Ich habe dazu bereits auf SO einen Post erstellt, ebenso beim Bug Tracker von VSCommunity:
Mehr Details befindet sich im SO Post. Sollte es erwünscht sein, werde ich diesen hier nochmals komplett schreiben.
Ich habe im Internet ein Beispielprojekt gefunden. Bei diesem tritt genau das selbe Phänomen auf!:
Ist das Problem bereits bei einem von euch aufgetreten?
Danke schonmal!
ThemeExtension.cs
[MarkupExtensionReturnType(typeof(Color))]
public class ThemeColorExtension : ThemeExtension
{
internal override object ModifyThemeValue(object value)
{
if (value is SolidColorBrush solidColorBrush)
return solidColorBrush.Color;
return value;
}
}
[MarkupExtensionReturnType(typeof(SolidColorBrush))]
public class ThemeExtension : MarkupExtension
{
// ##############################################################################################################################
// Properties
// ##############################################################################################################################
#region Properties
// ##########################################################################################
// Public Properties
// ##########################################################################################
/// <summary>
/// The Key in the Resource Theme file
/// </summary>
public string Key { get; set; }
// ##########################################################################################
// Private Properties
// ##########################################################################################
private static readonly List<ThemeExtension> _Cache = new List<ThemeExtension>();
private static readonly ResourceDictionary _DefaultTheme;
private static ResourceDictionary _CurrentTheme;
private PropertyInfo _Property { get; set; }
private DependencyProperty _DependencyProperty { get; set; }
private WeakReference _TargetReference { get; set; }
#endregion
// ##############################################################################################################################
// Constructor
// ##############################################################################################################################
#region Constructor
static ThemeExtension()
{
_DefaultTheme = new ResourceDictionary
{
Source = new Uri("/HtPlcFramework;component/Themes/DefaultTheme.xaml", UriKind.Relative)
};
_CurrentTheme = _DefaultTheme;
NavigationService.Navigated += _OnNavigated;
}
public ThemeExtension() { }
#endregion
// ##############################################################################################################################
// public methods
// ##############################################################################################################################
#region public methods
/// <summary>
/// https://social.msdn.microsoft.com/Forums/vstudio/en-US/931d7bff-90b6-4a70-bb0b-3a097e1301a1/net-40-breaking-change-using-a-markup-extension-as-value-of-property-setter-in-xaml-style?forum=wpf
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (target == null)
return this;
if (target.TargetObject != null && target.TargetProperty != null)
{
_TargetReference = new WeakReference(target.TargetObject);
if (target.TargetProperty.GetType() == typeof(PropertyInfo))
{
_Property = (PropertyInfo)target.TargetProperty;
}
else if (target.TargetProperty is DependencyProperty)
{
_DependencyProperty = (DependencyProperty)target.TargetProperty;
}
}
if (!_Cache.Contains(this))
_Cache.Add(this);
return ModifyThemeValue(_ReadThemeKey(Key));
}
/// <summary>
/// Change the Theme set
/// </summary>
/// <param name="themeUri">Default is: new Uri("/HtPlcFramework;component/Themes/DefaultTheme.xaml", UriKind.Relative)</param>
public static void ChangeTheme(Uri themeUri)
{
_CurrentTheme = new ResourceDictionary { Source = themeUri };
foreach (ThemeExtension reference in _Cache)
{
reference._UpdateTheme();
}
}
/// <summary>
/// Get the current theme entry. Can be null!
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static object ReadThemeKey(string key) => _ReadThemeKey(key);
internal virtual object ModifyThemeValue(object value)
{
return value;
}
#endregion
// ##############################################################################################################################
// private methods
// ##############################################################################################################################
#region private methods
private static void _OnNavigated(object sender, string layer)
{
_Cache.RemoveAll(ti => !ti._TargetReference.IsAlive);
}
private static object _ReadThemeKey(string key)
{
try
{
return _CurrentTheme[key] ?? _DefaultTheme[key];
}
catch (Exception)
{
Trace.WriteLine($"The key '{key}' was not found in {_CurrentTheme.Source}!");
return null;
}
}
private void _UpdateTheme()
{
if (_TargetReference.IsAlive)
{
if (_Property != null)
_Property.GetSetMethod().Invoke(_TargetReference.Target, new object[] { _ReadThemeKey(Key) });
else if (_DependencyProperty != null)
{
DependencyObject dependencyObject = _TargetReference.Target as DependencyObject;
dependencyObject?.SetValue(_DependencyProperty, _ReadThemeKey(Key));
}
}
else
{
_Cache.Remove(this);
}
}
#endregion
}
Auch wenn dieser Thread schon sehr alt ist..
Wenn es hierführ mittlerweile eine Lösung gibt, ich wäre auch daran interessiert!
Danke!
HTTPS? Muss im Fiddler extra aktiviert werden.
Ja das hatte ich gemacht, sonst wird mir auch die RestSharp Kommunikation nicht angezeigt. Dennoch wird mir die Kommunikation mit der Chilkat Bibliothek überhaupt nicht angezeigt.
Thread kann geschlossen werden!
Ich bedanke mich nochmal für eure Hilfe, dank Fiddler bin ich auf die Lösung gekommen.
Das Problem war das "/" am Ende:
Falsch
RestRequest request = new RestRequest("Orders/2013-09-01/", Method.POST);
Richtig
RestRequest request = new RestRequest("Orders/2013-09-01", Method.POST);
Falls jemand dennoch die Lösung weis, warum die externe Bibliothek nicht von Fiddler geloggt wird, bin ich gerne offen.
Gruß
d.jonas
Danke für eure Hilfe! Ich wollte gerade die beiden Requests miteinander vergleichen, jedoch wird der Traffic der "Chilkat" Bibliothek nicht aufgezeichnet, bei RestSharp geht das ohne Probleme. Gibt es hier spezielle Einstellungen vorzunehmen?
Folgendes habe ich schon probiert: http://docs.telerik.com/fiddler/Configure-Fiddler/Tasks/ConfigureDotNETApp Leider ohne Erfolg.
GlobalProxySelection.Select = new WebProxy("127.0.0.1", 8888);
<configuration>
<system.net>
<defaultProxy>
<proxy bypassonlocal="false" usesystemdefault="true" />
</defaultProxy>
</system.net>
</configuration>
Ich habe soeben mal diese Bibliothek (Chilkat C# library) getestet und musste feststellen, dass es damit funktioniert. Nun ist die Frage warum es bei mir nicht funktioniert?!
https://www.example-code.com/csharp/mws_list_orders.asp
Da dies mein erstes Projekt mit einer RestAPI ist, steh' ich ein bisschen auf dem Schlauch.
Danke im Voraus!
Hast Du mal auf GitHub nach vergleichbarem Code geschaut, wie dieser die Signatur ausrechnet?
Kann mir jetzt nicht vorstellen, dass es an der Amazon-Seite liegt.
Ich habe dazu kein GitHub Repo gefunden. Als Vorlage habe ich mir die Berechnung aus deren Beispiel geklaut https://developer.amazonservices.de/doc/bde/feeds/v20090101/cSharp.html. Ich denke auch nicht das diese falsch ist, da sowohl im C# als auch im Scratchpad die selbe Signatur errechnet wird.
Mal im Fiddler geschaut, ob die URL, die Du erzeugst, überhaupt gültig ist und so sind, wie Du es denkst.
Nein habe ich nicht. Wenn ich allerdings parameter vergesse zu übermitteln, erhalte eine dementsprechende Fehlermeldung. "Parameter Signatur fehlt", "Parameter Action fehlt; ist nicht gültig" etc..
Sieht etwas abenteuerlich aus, dass Du nicht einfach die Framework-Methoden für Queries und Parameter verwendest.
Was meinst du damit? Von welchen Framework-Methoden sprichst du? Ich benutze bspw. das Dictionary nur, da die Amazon Klasse ein Dict als Parameter zur Berechnung der Signatur verlangt.
Hallo zusammen,
ich möchte über die Amazon MWS API Schnittstelle sämtliche Daten (bspw. Orders) abrufen. Leider bekomme ich jedoch jedesmal diese Fehlermeldung:
<?xml version="1.0"?>
<ErrorResponse xmlns="https://mws.amazonservices.com/Orders/2013-09-01">
<Error>
<Type>Sender</Type>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.</Message>
</Error>
<RequestID>9f03f5b0-e4e4-4766-a554-00ff970b6b8c</RequestID>
</ErrorResponse>
Das wunder mich, da ich die selbe Signatur errechne, wie auch auf dem Amazon Scratchpad (https://mws.amazonservices.de/scratchpad/index.html) berechnet wird (Jedoch nur wenn ich den selben Zeitstempel eintrage, wie auch online generiert wird).
Folgend meine Methode und auch die Funktion zum errechnen der Signatur. Ich komme gerade nicht weiter und hoffe einer von euch weis woran es liegt.
Könnte es sein, dass es am RestCLient liegt? Dieser fügt standartmäßig noch 1-2 weitere Parameter hinzu?
public async void FetchOrders()
{
RestClient client = new RestClient("https://mws.amazonservices.de");
client.DefaultParameters.Clear();
client.ClearHandlers();
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("AWSAccessKeyId", "xxxxxxxxxx");
parameters.Add("Action", "ListOrders");
parameters.Add("CreatedAfter", "2018-01-01T11:34:00Z");
parameters.Add("MarketplaceId.Id.1", "A1PA6795UKMFR9");
parameters.Add("SellerId", "xxxxxxxxx");
parameters.Add("SignatureVersion", "2");
parameters.Add("Timestamp", DateTime.UtcNow.ToString("s") + "Z");
parameters.Add("Version", "2013-09-01");
RestRequest request = new RestRequest("Orders/2013-09-01/", Method.POST);
string signature = AmzLibrary.SignParameters(parameters, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
request.AddParameter("Signature", signature);
foreach (KeyValuePair<string, string> keyValuePair in parameters)
{
request.AddParameter(keyValuePair.Key, keyValuePair.Value);
}
IRestResponse result = await client.ExecuteTaskAsync(request);
}
public static class AmzLibrary
{
public static string GetParametersAsString(IDictionary<String, String> parameters)
{
StringBuilder data = new StringBuilder();
foreach (String key in (IEnumerable<String>)parameters.Keys)
{
String value = parameters[key];
if (value != null)
{
data.Append(key);
data.Append('=');
data.Append(UrlEncode(value, false));
data.Append('&');
}
}
String result = data.ToString();
return result.Remove(result.Length - 1);
}
public static String SignParameters(IDictionary<String, String> parameters, String key)
{
String signatureVersion = parameters["SignatureVersion"];
KeyedHashAlgorithm algorithm = new HMACSHA1();
String stringToSign = null;
if ("2".Equals(signatureVersion))
{
String signatureMethod = "HmacSHA256";
algorithm = KeyedHashAlgorithm.Create(signatureMethod.ToUpper());
parameters.Add("SignatureMethod", signatureMethod);
stringToSign = CalculateStringToSignV2(parameters);
}
else
{
throw new Exception("Invalid Signature Version specified");
}
return Sign(stringToSign, key, algorithm);
}
private static String CalculateStringToSignV2(IDictionary<String, String> parameters)
{
StringBuilder data = new StringBuilder();
IDictionary<String, String> sorted =
new SortedDictionary<String, String>(parameters, StringComparer.Ordinal);
data.Append("POST");
data.Append("\n");
Uri endpoint = new Uri("https://mws.amazonservices.de/Orders/2013-09-01");
data.Append(endpoint.Host);
if (endpoint.Port != 443 && endpoint.Port != 80)
{
data.Append(":")
.Append(endpoint.Port);
}
data.Append("\n");
String uri = endpoint.AbsolutePath;
if (uri == null || uri.Length == 0)
{
uri = "/";
}
data.Append(UrlEncode(uri, true));
data.Append("\n");
foreach (KeyValuePair<String, String> pair in sorted)
{
if (pair.Value != null)
{
data.Append(UrlEncode(pair.Key, false));
data.Append("=");
data.Append(UrlEncode(pair.Value, false));
data.Append("&");
}
}
String result = data.ToString();
return result.Remove(result.Length - 1);
}
private static String UrlEncode(String data, bool path)
{
StringBuilder encoded = new StringBuilder();
String unreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~" + (path ? "/" : "");
foreach (char symbol in System.Text.Encoding.UTF8.GetBytes(data))
{
if (unreservedChars.IndexOf(symbol) != -1)
{
encoded.Append(symbol);
}
else
{
encoded.Append("%" + String.Format("{0:X2}", (int)symbol));
}
}
return encoded.ToString();
}
private static String Sign(String data, String key, KeyedHashAlgorithm algorithm)
{
Encoding encoding = new UTF8Encoding();
algorithm.Key = encoding.GetBytes(key);
return Convert.ToBase64String(algorithm.ComputeHash(
encoding.GetBytes(data.ToCharArray())));
}
}
ok dann danke erstmal!
Werden wir uns wohl doch einen Anwanlt mal ins Haus bestellen müssen.
Grüße
Hallo zusammen,
ich beziehe mich auf diesen Thread Wo Lizenzen von 3rd-Party-Bibliotheken unterbringen?.
Ich habe in meiner Anwendung folgende OpenSource Bibliotheken verwendet:
*Harvester
*NLog
*Snap7
*Newtonsoft.Json
In meiner Anwendung selbst habe ich keine Lizenzhinweise verwiesen. Dafür möchte ich gerne das "Handbuch" zur Software verwenden. Ist das ausreichend? Anschließend einen Punkt "Anhang" erstellen mit den Links zu allen Lizenzen ergänzen.
In meiner Doku habe ich folgenden Abatz (unter den rechtlichen Hinweisen) stehen:
Dieses Produkt enthält möglicherweise Drittanbietersoftware, für die Musterfirma GmbH einen entsprechenden Hinweis („Programme anderer Hersteller“) zur Verfügung stellen muss. Einige Drittanbieterprogramme werden als Open Source oder mit kostenlosen Softwarelizenzen bereitgestellt. Die Lizenzvereinbarung, die der Software beiliegt, ändert keine Rechte oder Verpflichtungen, die Sie im Rahmen dieser Open Source- oder kostenlosen Softwarelizenzen haben können. Weitere Informationen zu den Programmen anderer Hersteller erhalten Sie in den entsprechenden Rechtshinweisen im Anhang.
Eine weitere Frage. Ich habe folgenden Satz:
kann auf beliebiger Microsoft Windows kompatibler Hardware verwendet werden. Je nach Funktionsumfang steigen die Anforderungen an die eingesetzte Hardware. Bei kleinen Anlagen kann diese beispielsweise auf einem PC mit Intel® Atom™ CPU installiert werden.
Muss ich hierfür auch einen Lizenzhinweis zu Microsoft oder Intel verweisen?! Stehe etwas auf dem Schlauch und finde im Netz keine Antwort dazu.
Danke!!
Gruß
Dominic
Hat das einen Grund, dass Du zB nicht FluentMigrator nutzt, der sich darum automatisiert kümmert?
Ich habe mich heute Morgen gleich mal hin gesetzt und den FluentMigrator näher angeschaut. Bis jetzt bin ich von dem Konzept ehrlich gesagt begeistert. Beim ApplicationStart kann ich die neue Tabelle gleich erstellen lassen. Sowohl in Postgres als auch in SQLite funktioniert dies einwandfrei. Auch in einer zweiten Migrierung "Add Column" gibts keine Probleme und keine neue Exception dank der Versionierung.
Aber: Query for dropping Sqlite columns is not supported by Sqlite
https://github.com/fluentmigrator/fluentmigrator/issues/7
Möchte ich in einer 3ten Migrierung eine Column wieder löschen, wird dies von SQLite nicht untersützt. Hast du dafür einen Workaround? Anderseits ob dies jemals gemacht werden muss sei dahin gestellt.
Frage: Wenn ich das richtig verstanden habe, benutzt man den FluentMigrator ausschließlich dafür und die Tatsache die SQL-Queries auszuprogrammieren bleibt?
Was ihr braucht ist eine Versionierung der Tabellen.
Dazu wäre es ratsam, dass ihr eine Versiontabelle mit der Infor Tabelle + aktuelle Version pflegt.
Eure Migrationen müssten dann ebenfalls pro Version die beim Start der Anwendung durchlaufen werden und nur neue Migrationen durchgeführt werden.
Das wird dank dem FluentMigrator nun auch erledigt. Danke trotzdem!
Als Horror empfand ich EF Core bisher nicht.
War sogar recht simpel und extrem flexibel.
Ein sehr großes Problem war das man nie nur ein Teil von Daten lesen konnte, sondern immer alles (also zum Beispiel auch Bilder die in der Spalte mit abgelegt sind) mit laden musste. Das hat enormen Traffic verursacht..
Wie Abt schon schrieb:
EF Core kann aktuell nicht mal Group By (technologisch schon, aber es werden alle Daten auf den Client geladen und dort gruppiert. EF Core kann kein Group By auf DB Ebene).
So damit meine existierene Datenbank (Die auch schon bereits bei mehreren Kunden vorhanden ist und mit Daten befüllt; SQLite) funktioniert würde, sollten folgende Befehle ausreichend sein, oder irre ich mich da?
Diese würde ich zusätzlich bei SQLite durchlaufen lassen
CREATE TABLE IF NOT EXISTS "VersionInfo" (
"Version" INTEGER NOT NULL,
"AppliedOn" DATETIME,
"Description" TEXT
);
CREATE UNIQUE INDEX IF NOT EXISTS "UC_Version" ON "VersionInfo" ("Version" ASC);
INSERT OR IGNORE INTO `VersionInfo` VALUES (1,strftime('%Y-%m-%dT%H:%M:%S', datetime()),'_Migration');
EDIT:
Leider musste ich feststellen das man keine Trigger anlegen kann. Das ist dann doch ziemlich bescheiden..
Postgres
CREATE OR REPLACE FUNCTION update_component_timestamp_proc()
RETURNS TRIGGER AS $$
BEGIN
NEW.modified_on = now();
RETURN NEW;
END;
$$ language 'plpgsql';
DROP TRIGGER IF EXISTS update_component_timestamp ON components;
CREATE TRIGGER update_component_timestamp AFTER UPDATE ON components FOR EACH ROW EXECUTE PROCEDURE update_component_timestamp_proc();
SQLite
CREATE TRIGGER IF NOT EXISTS "components_modified_on" AFTER UPDATE ON "components"
BEGIN
UPDATE "components" SET "modified_on" = CURRENT_TIMESTAMP WHERE "ident" = NEW."ident";
END;
Also den FluentMigrator kenne ich nicht, habe ich auch noch nie gehört. Wir hatten am Anfang das EntityFramework im Einsatz. Mussten jedoch mit Erschrecken feststellen, dass es ein Horror ist und unseren Zwecken nicht gerecht wurde (anderes Thema). Demzufolge haben wir uns dazu entschieden alle Abfragen selbst zu schreiben damit wir genau wissen was passiert.
Ich habe mir jetzt schon ein Lösungsansatz überlegt. Ich verschiebe die "000_InitialCreate.sql" Dateien in einen separaten Ordner "_InitialCreate" und lasse diese immer zuerst durchlaufen. Diese erzeugen mir auch keine Exceptions (i.d.R.).
Im _Migrations Ordner bekommen die Abfragen eine neue Syntax. Jede Datei besteht nun quasi aus zwei Dateien. 1. "xxx_foo.sql" 2. "xxx_foo_condition.sql". Nur wenn die condition sql eine true zurück liefert, führe ich die zugehörige Datei auch aus.
In meinem Beispiel:
"001_ExtendRecipe.sql" und "001_ExtendRecipe_condition.sql".
In der "*condition.sql" steht nur eine Abfrage drinnen die überprüft ob die Spalte schon vorhanden ist oder eben nicht. Dann kann ich dieses Schema weiter verfolgen und bleibe dynamisch.
Gruß
Hallo zusammen,
ich wende mich an euch da ich nicht mehr weiter weis und ich wissen möchte ob ich mein Konzept falsch aufgezogen habe und dies anpassen muss.
Nun zu meiner Anwendung:
Aktuell ist diese nur lokal (SQLite) nutzbar. In einer weiteren Ausbaustufe ist es nun möglich diese mit einer PostgresSQL Datenbank zu verbinden.
Sobald die Anwendung startet wird geprüft ob wir lokal oder remote arbeiten möchten und dementsprechend wird der SQLite oder Postgres Factory geladen.
Anschließend wird die Datenbankstruktur erstellt (siehe "_CacheDbRessources()" und "_RunMigrations"), sollte diese noch nicht erstellt worden sein und weitere Migrationen werden durchgeführt.
/// <summary>
/// Reads all ressource names from the assembly and saves those containing the db namespace in a private list
/// </summary>
private void _CacheDbRessources()
{
_RessourcesNamespace = $"Foo.Data.{_ProviderInvariantName}";
_RessourceNames = Assembly.GetExecutingAssembly().GetManifestResourceNames().Where(item => item.Contains(_RessourcesNamespace));
}
/// <summary>
/// Does the first-time setup for the database and runs migrations.
/// </summary>
private void _RunMigrations()
{
IEnumerable<string> migrations =
_RessourceNames.Where(item => item.Contains("_Migrations"))
.Select(s => s.Replace(_RessourcesNamespace + ".", ""))
.OrderBy(item => item);
if (migrations.Any())
_Logger.Debug("Running database migrations...");
using (DbConnection conn = GetConnection())
{
foreach (string item in migrations)
{
using (DbTransaction tran = conn.BeginTransaction())
{
_Logger.Debug("Executing query '{0}'...", item);
using (DbCommand cmd = conn.CreateCommand(GetQueryResource(item), tran))
{
int rows = cmd.ExecuteNonQuery();
_Logger.Debug($"{rows} rows where affected.");
}
tran.Commit();
}
}
}
}
Auschnitt aus 000_InitalCreate.sql
/* users */
CREATE TABLE IF NOT EXISTS "users" (
"ident" INTEGER PRIMARY KEY AUTOINCREMENT,
"user_name" TEXT NOT NULL UNIQUE,
"real_name" TEXT,
"password" TEXT NOT NULL,
"userlevel" INTEGER NOT NULL DEFAULT 0,
"enabled" INTEGER NOT NULL DEFAULT 1,
"language" TEXT,
"lock" TEXT
);
Die Idee dahinter, sobald der Kunde zum Beispiel MSSQL Support wünscht, erzeuge ich einen Ordner "MSSQL" und erstelle dort eine "000_InitialCreate.sql" Datei und den weiteren angepassten Abfragen.
Mein Problem
In der neuen Ausbaustufe muss eine zusätzliche Spalte ("skiptime") in der Tabelle "recipes" angelegt werden. In der postgres 000_InitalCreate.sql konnte ich dies ohne Probleme einfach einfügen
/* recipes */
CREATE TABLE IF NOT EXISTS "recipes" (
"ident" SERIAL PRIMARY KEY,
"identifier" TEXT NOT NULL UNIQUE,
"name" TEXT,
"description" TEXT,
"crosshair_color" TEXT NOT NULL DEFAULT '#00ff00',
"crosshair_weight" REAL NOT NULL DEFAULT 1.0,
"crosshair_padding" REAL NOT NULL DEFAULT 2.5,
"created_on" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
"created_by" TEXT,
"modified_on" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
"modified_by" TEXT,
"blocks" TEXT,
"blocks_mode" INTEGER NOT NULL DEFAULT 0,
"lock" TEXT,
"skiptime" INTEGER NOT NULL DEFAULT 5
);
jedoch ist diese Spalte in der SQLite Datenbank noch nicht vorhanden. Über folgende Query
ALTER TABLE recipes ADD COLUMN skiptime INTEGER NOT NULL DEFAULT 5;
kann ich diese einfügen. Bei einem erneuten Aufruf würde dies jedoch eine Exception werfen, da die Spalte bereits vorhanden ist. In SQLite ist es nicht möglich mit IF,ELSE zu arbeiten und die Logik im C# Teil ausarbeiten wollte ich vermeiden.
Nun hat einer eine Idee wie ich das Problem lösen kann?
Danke
Gruß D.Jonas
Schaue mal unter den Properties der Styles.xaml bzw. Colors.xaml nach, was als Build Action eingestellt ist. Page ist hier die richtige Wahl. Manchmal verstellt sich das, z.B. wenn man Resourcen-Dateien kopiert/in andere Projekte zieht.
Du hast Recht, hier stand noch "Resource" drinnen (warum auch immer), bei allen anderen "Page".
ReSharper meckert nun auch nichtmehr, er kann jedoch die Farbe nicht "live" im XAML Code anzeigen, was mich jetzt aber nicht stört.
Leider nicht.
Ich habe mich jetzt damit beholfen das ich die Colors.xml direkt in meine MainAppFramework.dll importiert habe und dort separat eingebunden habe:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
Styles und Controls werden gefunden, binde ich diese über die Framework.dll app ein.
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Framework;component/Styles/Button.xaml"/>
</ResourceDictionary.MergedDictionaries>
Da ich ReSharper benutze, ist es denkbar, dass dieser zu doof ist die Colors.xaml (SolidColorBrush) richtig zu linken..
Ich danke trotzdem für die Hilfe
Bist du im MainAppFramework mal direkt auf die Colors.xaml gegangen?
so würde es funktionierten
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Framework;component/Styles/Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
Allerdings unterstreicht er mir im XAML Code die Resource.> Fehlermeldung:
"Resource 'Color1' is not found". Zur Laufzeit funktioniert es allerdings. Ist etwas unschön, da ich so einige Fehler habe während dem entwickeln.. vll gibt es ja noch andere Möglichkeiten?
Hallo zusammen,
da ich nichtmehr weiter weis, hoffe ich hier Hilfe zu finden 😃
Ich habe:
In der Framework.dll sind globale Styles, Controls etc. abgelegt die ich in mehren Apps verwende. Die MainAppFramework.dll hat MainApp spezifische Styles und Controls.
Nun habe ich die Framework.dll in der MainAppFramework.dll als Verweis eingebunden, kann jedoch nicht auf die Styles in den ResourcenDictionaries zugreifen. Auf die Controls und UserControls kann ich zugreifen, jedoch nicht auf die Resourcen wie bspw. SolidColorBrush.
Colors.xaml
unter Framework.dll/Styles/Colors.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="Color1" Color="#FF424242"/>
<SolidColorBrush x:Key="Color2" Color="#FF555555"/>
</ResourceDictionary>
Styles.xaml
unter Framework.dll/Styles.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/Button.xaml"/>
<ResourceDictionary Source="Styles/DataGrid.xaml"/>
<ResourceDictionary Source="Styles/Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
ResourceDict in MainAppFramework.dll
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:Framework.Controls;assembly=Framework">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Framework;component/Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type Controls:SpecialButton}">
</Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="{StaticResource Color1}"/>
</Style>
</ResourceDictionary>
Der SpecialButton wird gefunden, Color1 hingegen wird nicht gefunden und löst eine Exception aus.
In der MainApp werden sowohl die Farben als auch die Controls richtig gefunden und zugeordnet.
Danke für eure Hilfe!!
Gruß
DJ