Architecture

When designing a Windows Forms application, you have several architectural patterns to choose from. The common ones are:

Model-View-Controller (MVC):

  • Description: MVC separates the application into three components:
    • Model: Represents the data and business logic.
    • View: Handles the presentation and user interface.
    • Controller: Manages user input and communicates between the Model and View.
  • Pros:
    • Clear separation of concerns.
    • Reusable components.
    • Supports unit testing.
  • Cons:
    • Can become complex for small applications.
    • Requires careful design to avoid tight coupling.
  • Use Case: Suitable for medium to large applications with complex interactions.

Clean Architecture:

  • Description: Clean Architecture emphasizes separation of concerns and independence from external frameworks.
  • Entities: Represent core business logic.
  • Use Cases (Interactors): Application-specific business rules.
  • Interfaces (Gateways): Define external interfaces (e.g., database, UI).
    • Frameworks & Drivers: External frameworks and tools (e.g., Windows Forms, databases).
    • Pros:
      • Highly modular and testable.
      • Adaptable to changes in external frameworks.
      • Focuses on business logic.
    • Cons:
      • Initial setup complexity.
      • May be overkill for small projects.
    • Use Case: Suitable for large, long-lived applications with evolving requirements.
May 3, 2024

Subsections of Architecture

Migrations

In .NET 8, when a migration attempts to create a table that already exists in the database, the behavior depends on the state of the migration history and the existing database schema:

Initial Migration

When you apply the initial migration, Entity Framework Core (EF Core) creates the specified tables based on your model. If the table already exists in the database, EF Core will not re-create it. It only creates tables that are not present.

Subsequent Migrations

For subsequent migrations (e.g., adding columns, modifying schema), EF Core generates migration scripts based on the difference between the current model and the previous migration’s snapshot. If a table is already in the database and corresponds to the model, EF Core will not attempt to create it again. However, if the table structure differs from the model (e.g., missing columns), EF Core will generate migration scripts to update the schema.

Migration History

EF Core maintains a special table called __EFMigrationsHistory (or __migrations_History in some databases). This table tracks which migrations have been applied to the database. If a migration has already been applied (recorded in this table), EF Core will skip creating the corresponding tables.

Rollback (Down) Method

The Down method in a migration handles rolling back changes. If you need to undo a migration, the Down method drops the corresponding tables. For example, if you remove a column in a migration, the Down method will drop that column.

Manual Truncation or Deletion

Be cautious when manually truncating or deleting tables (including the __EFMigrationsHistory table). If the migration history is lost, EF Core may treat subsequent migrations as initial migrations, leading to re-creation of existing tables. In summary, EF Core is designed to be aware of the existing database schema and avoid unnecessary table creation. Ensure that the migration history is intact, and avoid manual truncation of the migration history table to prevent unexpected behavior during migrations

Mar 26, 2024

Diversity workbench

Hierarchy

The extraction of the hierarchies in some cases are very time consuming. To speed up the queries including hierarchies these are cached in cache tables. Therefor procedures are available to set the hierarchies in the cache tables to the current state after any changes in the original data.

The example below shows the hierarchy in the table collection in DC. The procedure procRefreshCollectionHierarchyCache checks the function CollectionHierarchyRefillBlocked. In case it returns true, nothing is changed as another process is active. In case of false, the function CollectionHierarchyRefillBlocked is set to true and the data in the cache table are refilled with the current state of the data. As a last step, the function CollectionHierarchyRefillBlocked is set to false.

graph TD;
    Start(Try to set<br>the hierarchy to<br>the current state)
    Cache[<i class="fa-fw fas fa-table"></i> Cache table]
    proc[proc Refresh<br>cache table] 
    procCheck(Check state)
    procSetTrue(Set to true)
    procRefill[Refill<br>cache table<br>with current data]
    procSetFalse(Set to false)
    func{<i class="fa-fw fas fa-eye"></i> function<br>Refill<br>Blocked}
    Start --> func
    func -->|false| proc
    func -->|true| Start
    proc --> |check if false, set to true| procCheck
    procCheck --> func
    procCheck --> |true| Start
    procCheck --> |false| procSetTrue
    procSetTrue --> func
    procSetTrue --> procRefill
    procRefill --> Cache
    procRefill --> procSetFalse
    procSetFalse --> func

The procedure is executed only by users with the necessary permissions. The cache needs an update after any changes to the original data e.g. via an import. So e.g. the ImportWizard should either include the execution of the procedures as a last step after the import or the user should get an according message to update the cache.

Implemented in:

DC

CollectionHierarchy

Jan 30, 2025

Diversity workbench

Keywords

Keywords in Hugo

Die Schlüsselwörter werden zum Test in der Datei keywords_ori_dwb erfasst und dann angepasst in die Datei keywords_dwb kopiert. Hugo übersetzt die dort angegebenen Adressen. Ein Service auf dem Apacheserver übersetzt dies dann in eine Datei keywords.txt mit Paaren aus Schlüsselwort und dem zugehörigen Link. Dies wird dann als Service zur Verfügung gestellt und kann von den Applikationen benutzt werden.

Keywords in den Applikationen

  • die Formulare benötigen einen Helpprovider
  • die Keywords werden als Property vermittelt durch den Helpprovider direkt beim Formular oder in den controls eingetragen

Event handler

Bei der ersten Verwendung von F1 in einem Formular werden durch den KeyDown-Handler des Formulars das Formular und die enthaltenen Controls sofern ein Keyword eingetragen wurde mit einem Eventhandler versorgt die dann für den Aufruf des Manuals sorgen.

Da die Funktion erst beim KeyDown im Formular gestartet wird und asynchron ist bremst sie den Start der Applikationen nicht.

aktuelle Probleme:

  • Beim allerersten Aufruf des Manuals wird auch die Hauptseite für das Formular geöffnet sofern dort ein Keyword hinterlegt ist

Beispiel für Umsetzung anhand DiversityCollection_GUC_8 im Formular FormArtCode:

Code im Formular

in the constructor or in the properties of the form

this.KeyPreview = true;

im Code

link the KeyDown event of the form to the Form_KeyDown event

#region Manual

/// <summary>
/// Adding event deletates to form and controls
/// </summary>
/// <returns></returns>
private async Task InitManual()
{
    try
    {

        DiversityWorkbench.DwbManual.Hugo manual = new Hugo(this.helpProvider, this);
        if (manual != null)
        {
            await manual.addKeyDownF1ToForm();
        }
    }
    catch (Exception ex) { DiversityWorkbench.ExceptionHandling.WriteToErrorLogFile(ex); }
}

/// <summary>
/// ensure that init is only done once
/// </summary>
private bool _InitManualDone = false;


/// <summary>
/// KeyDown of the form adding event deletates to form and controls within the form
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Form_KeyDown(object sender, KeyEventArgs e)
{
    try
    {
        if (!_InitManualDone)
        {
            await this.InitManual();
            _InitManualDone = true;
        }
    }
    catch (Exception ex) { MessageBox.Show(ex.Message); }
}

#endregion

Zentraler code in e.g. Klasse Manual

#region Adding functions

/// <summary>
/// If the control contains a keyword related to the helpprovider of the form
/// </summary>
/// <param name="control"></param>
/// <param name="helpProvider"></param>
/// <returns></returns>
private bool IsControlLinkedToHelpKeyword(Control control, HelpProvider helpProvider) 
{ 
    string helpKeyword = helpProvider.GetHelpKeyword(control); 
    return !string.IsNullOrEmpty(helpKeyword); 
}

/// <summary>
/// If the form contains a keyword related to the helpprovider
/// </summary>
/// <param name="form"></param>
/// <param name="helpProvider"></param>
/// <returns></returns>
private bool IsFormLinkedToHelpKeyword(Form form, HelpProvider helpProvider)
{
    string helpKeyword = helpProvider.GetHelpKeyword(form);
    return !string.IsNullOrEmpty(helpKeyword);
}

/// <summary>
/// Constructor
/// </summary>
/// <param name="helpProvider">The helpprovider of the form</param>
/// <param name="form">The form where the event handlers should be added</param>
public Manual(HelpProvider helpProvider, Form form)
{
    _helpProvider = helpProvider;
    _form = form;
}

/// <summary>
/// HelpProvider of the form
/// </summary>
private HelpProvider _helpProvider;

/// <summary>
/// the form containing the HelpProvider
/// </summary>
private System.Windows.Forms.Form _form;

/// <summary>
/// adding the event delegates to form and controls
/// </summary>
/// <returns></returns>
public async Task addKeyDownF1ToForm()
{
    try
    {
        if (_form != null && _helpProvider != null)
        {
            if (IsFormLinkedToHelpKeyword((Form)_form, _helpProvider))
            {
                _form.KeyUp += new KeyEventHandler(form_KeyDown);
            }
            foreach (System.Windows.Forms.Control C in _form.Controls)
            {
                await addKeyDownF1ToControls(C);
            }
        }
    }
    catch (Exception ex) { MessageBox.Show(ex.Message); }
}

/// <summary>
/// Adding Event delegates to the controls
/// </summary>
/// <param name="Cont">the control to which the delegate should be added it a keyword is present</param>
/// <returns></returns>
private async Task addKeyDownF1ToControls(System.Windows.Forms.Control Cont)
{
    try
    {
        foreach (System.Windows.Forms.Control C in Cont.Controls)
        {
            if (IsControlLinkedToHelpKeyword(C, _helpProvider))
            {
                C.KeyDown += new KeyEventHandler(control_KeyDown);
            }
            await addKeyDownF1ToControls(C);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

/// <summary>
/// Opening the manual if F1 is pressed within the form
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void form_KeyDown(object sender, KeyEventArgs e)
{
    try
    {
        if (!e.Handled && e.KeyCode == Keys.F1) 
        {
            string Keyword = _helpProvider.GetHelpKeyword(_form);
            OpenManual(Keyword);
        }
    }
    catch (Exception ex) { MessageBox.Show(ex.Message); }
}


/// <summary>
/// Opening the manual if F1 is pressed within the control
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void control_KeyDown(object sender, KeyEventArgs e)
{
    try
    {
        if (e.KeyCode == Keys.F1)
        {
            string Keyword = _helpProvider.GetHelpKeyword((System.Windows.Forms.Control)sender);
            OpenManual(Keyword);
            e.Handled = true;
        }
    }
    catch (Exception ex) { MessageBox.Show(ex.Message); }
}

#endregion

zentrale Funktionen

  • holen keyword Verzeichnis via HtmlAgilityPack
  • öffnen Webseite anhand von Keyword

vorerst mit HtmlAgilityPack gelöst. Service hierzu kommt noch

#region HtmlAgilityPack

/// <summary>
/// Prefix for websites
/// </summary>
private static readonly string HugoManualUrlPrefix = "https://www.diversityworkbench.de";

/// <summary>
/// The URL of the site containing the keywords
/// </summary>
private static readonly string _UrlForKeywords = "https://www.diversityworkbench.de/manual/dwb_latest/diversityworkbench/keywords_dwb/index.html";

private static Dictionary<string, string> _KeywordsFromHugo;

/// <summary>
/// The dictionary of the keyword/Links scraped from the website in the Hugo manual
/// </summary>
private static Dictionary<string, string> KeywordsFromHugo
{
    get
    {
        if (_KeywordsFromHugo == null)
        {
            HtmlWeb HtmlWeb = new HtmlWeb();
            HtmlAgilityPack.HtmlDocument htmlDocument = HtmlWeb.Load(_UrlForKeywords);
            var Links = htmlDocument.DocumentNode.SelectNodes("//html//body//div//main//div//article//ul//a");
            if (Links != null)
            {
                _KeywordsFromHugo = new Dictionary<string, string>();
                foreach (var Link in Links)
                {
                    string Key = Link.InnerText;
                    string URL = HugoManualUrlPrefix + Link.GetAttributeValue("href", string.Empty);
                    if (Key != null && Key.Length > 0 && 
                        URL != null && URL.Length > 0 && 
                        !_KeywordsFromHugo.ContainsKey(Key))
                    {
                        _KeywordsFromHugo.Add(Key, URL);
                    }
                }
            }
        }
        return _KeywordsFromHugo;
    }
}

/// <summary>
/// Open a manual site
/// </summary>
/// <param name="keyword">The keyword linked to the site in the manual</param>
public void OpenHugoManual(string keyword)
{
    if (KeywordsFromHugo == null) { return; }
    if (KeywordsFromHugo.ContainsKey(keyword))
    {
        string Link = DiversityCollection_GUC.Hugo.Manual.KeywordsFromHugo[keyword];
        try
        {
            if (Link.Length > 0)
            {
                Process.Start(new ProcessStartInfo
                {
                    FileName = Link,
                    UseShellExecute = true
                });
            }
        }
        catch (Exception ex) { MessageBox.Show("Error opening URL: " + ex.Message); }
    }
}



#endregion
graph TD;
    Start[Open form]
    Handler["Creation of handlers"]
    Manual[Open the manual]
    F1[Click F1 in form or control]
    Scrape[Using the<br>***HtmlAgilityPack***<br>to scrape the dictionary<br>for the keywords<br>from the<br>Hugo keyword site<br>at first call]
    Key[Use keyword to get link]
    Start --> | using F1 in the form for the first time | Handler 
    Handler --> Scrape
    F1 --> Scrape
    Scrape --> Key
    Key --> Manual

Alternative nicht verwendete Optionen

Im frontmatter der Seiten können für einzelne Kapitel Schlüsselwörter hinterlegt werden:

---
title: ...
linktitle: ...
keywords: 
    - Nachweis_GUC
    - Fundort_GUC
---

Was wir benötigen ist ein Schlüsselwort in der Applikation und dazu ein Link zum Manual. Aus den CHM Dateien lassen sich die bereits jetzt vorhandenen Schlüssel extrahieren (Außer den von Wolfgang umgebauten Modulen DSP und DG).

Eine Alternative zum manuellen Aufbau wäre ein Scan aller vorhandenen Seiten und die Extraktion der verwendeten Links.

Options

Examlpe for GUC

damit werden automatisch die korrekten Links im html erzeugt und könnten dann extrahiert werden

Aktuellen Liste: keywords_dwb

Contact_DA modules/diversitycollection/Contact_DA Agent_DA modules/diversitycollection/Agent_DA Archive_DA modules/diversitycollection/Archive_DA Backup_DA modules/diversitycollection/Backup_DA Contact_DA modules/diversitycollection/Contact_DA DataWithholding_DA modules/diversitycollection/DataWithholding_DA AgentDisplayTypes_DA modules/diversitycollection/AgentDisplayTypes_DA Descriptors_DA modules/diversitycollection/Descriptors_DA ExternalData_DA modules/diversitycollection/ExternalData_DA Feedback_DA modules/diversitycollection/Feedback_DA Hierarchy_DA modules/diversitycollection/Hierarchy_DA History_DA modules/diversitycollection/History_DA Images_DA modules/diversitycollection/Images_DA …

hier müssen die Links noch angepasst werden. Insofern noch nicht wirklich brauchbar