init
This commit is contained in:
339
GLM5Analysis/PatternLibrary/DataAccessPattern.md
Normal file
339
GLM5Analysis/PatternLibrary/DataAccessPattern.md
Normal file
@@ -0,0 +1,339 @@
|
||||
# Data Access Pattern
|
||||
|
||||
## When to Use
|
||||
- Retrieving or storing data in SQL Server database
|
||||
- Implementing data layer for business entities
|
||||
- Creating CRUD operations for domain objects
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ ViewModel │────>│ DbAPI │────>│ SQL Server │
|
||||
│ (UI Layer) │ │ (Data Layer) │ │ (Database) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│
|
||||
├── Connections/
|
||||
├── Sensors/
|
||||
├── Groups/
|
||||
├── TestSetups/
|
||||
└── Channels/
|
||||
```
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
### Structure
|
||||
```
|
||||
DbAPI/
|
||||
├── DbAPI.cs (Main facade class)
|
||||
├── Connections/
|
||||
│ └── ConnectionManager.cs (Connection handling)
|
||||
├── {Entity}/
|
||||
│ └── {Entity}.cs (Data access implementation)
|
||||
└── Errors/
|
||||
└── ErrorCodes.cs (Error code constants)
|
||||
```
|
||||
|
||||
## Code Templates
|
||||
|
||||
### 1. Interface Definition
|
||||
**File:** `DTS.Common/Interface/{Entity}/I{Entity}.cs`
|
||||
```csharp
|
||||
using DTS.Common.Interface.Database;
|
||||
|
||||
namespace DTS.Common.Interface.{Entity}
|
||||
{
|
||||
public interface I{Entity}
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all {entity} records
|
||||
/// </summary>
|
||||
/// <param name="user">User making the request</param>
|
||||
/// <param name="connection">Database connection details</param>
|
||||
/// <param name="records">Output array of records</param>
|
||||
/// <returns>Error code (0 = success)</returns>
|
||||
ulong {Entity}Get(IUserDbRecord user, IConnectionDetails connection,
|
||||
out I{Entity}Record[] records);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new {entity} record
|
||||
/// </summary>
|
||||
ulong {Entity}Add(IUserDbRecord user, IConnectionDetails connection,
|
||||
I{Entity}Record record, out int newId);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing {entity} record
|
||||
/// </summary>
|
||||
ulong {Entity}Update(IUserDbRecord user, IConnectionDetails connection,
|
||||
I{Entity}Record record);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a {entity} record
|
||||
/// </summary>
|
||||
ulong {Entity}Delete(IUserDbRecord user, IConnectionDetails connection, int id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Data Access Implementation
|
||||
**File:** `DbAPI/{Entity}/{Entity}.cs`
|
||||
```csharp
|
||||
using DbAPI.Connections;
|
||||
using DbAPI.Errors;
|
||||
using DbAPI.Logging;
|
||||
using DTS.Common.Interface.Database;
|
||||
using DTS.Common.Interface.{Entity};
|
||||
using DTS.Common.Utilities.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace DbAPI.{Entity}
|
||||
{
|
||||
internal class {Entity} : I{Entity}
|
||||
{
|
||||
private const int DB_VERSION_{FEATURE} = 100;
|
||||
|
||||
public ulong {Entity}Get(IUserDbRecord user, IConnectionDetails connection,
|
||||
out I{Entity}Record[] records)
|
||||
{
|
||||
records = new I{Entity}Record[0];
|
||||
var list = new List<I{Entity}Record>();
|
||||
|
||||
if (!DbAPI.Connections.IsUserLoggedIn(user, connection))
|
||||
{
|
||||
return ErrorCodes.ERROR_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
var ret = ConnectionManager.GetSqlCommand(connection, out var cmd, "sp_{Entity}Get");
|
||||
if (ret != ErrorCodes.ERROR_SUCCESS) return ret;
|
||||
|
||||
try
|
||||
{
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
var reader = cmd.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
list.Add(new {Entity}Record(reader));
|
||||
}
|
||||
|
||||
records = list.ToArray();
|
||||
return ErrorCodes.ERROR_SUCCESS;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Log(TraceEventType.Error, LogManager.LogEvents.{Entity},
|
||||
$"sp_{Entity}Get failed: {ex.Message}");
|
||||
return ErrorCodes.ERROR_UNKNOWN;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmd.Connection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public ulong {Entity}Add(IUserDbRecord user, IConnectionDetails connection,
|
||||
I{Entity}Record record, out int newId)
|
||||
{
|
||||
newId = 0;
|
||||
|
||||
if (!DbAPI.Connections.IsUserLoggedIn(user, connection))
|
||||
return ErrorCodes.ERROR_ACCESS_DENIED;
|
||||
|
||||
var ret = ConnectionManager.GetSqlCommand(connection, out var cmd, "sp_{Entity}Add");
|
||||
if (ret != ErrorCodes.ERROR_SUCCESS) return ret;
|
||||
|
||||
try
|
||||
{
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
cmd.Parameters.Add(new SqlParameter("@Name", SqlDbType.NVarChar) { Value = record.Name });
|
||||
|
||||
var idParam = new SqlParameter("@Id", SqlDbType.Int)
|
||||
{
|
||||
Direction = ParameterDirection.Output
|
||||
};
|
||||
cmd.Parameters.Add(idParam);
|
||||
|
||||
cmd.ExecuteNonQuery();
|
||||
newId = (int)idParam.Value;
|
||||
|
||||
return ErrorCodes.ERROR_SUCCESS;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Log(TraceEventType.Error, LogManager.LogEvents.{Entity},
|
||||
$"sp_{Entity}Add failed: {ex.Message}");
|
||||
return ErrorCodes.ERROR_UNKNOWN;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmd.Connection.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddNullableIntParameter(SqlCommand cmd, string paramName, int? value)
|
||||
{
|
||||
cmd.Parameters.Add(new SqlParameter(paramName, SqlDbType.Int)
|
||||
{
|
||||
Value = value.HasValue ? (object)value.Value : DBNull.Value
|
||||
});
|
||||
}
|
||||
|
||||
protected void AddNullableStringParameter(SqlCommand cmd, string paramName, string value)
|
||||
{
|
||||
cmd.Parameters.Add(new SqlParameter(paramName, SqlDbType.NVarChar)
|
||||
{
|
||||
Value = string.IsNullOrEmpty(value) ? (object)DBNull.Value : value
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Register in DbAPI.cs Facade
|
||||
**File:** `DbAPI/DbAPI.cs`
|
||||
```csharp
|
||||
private readonly {Entity}.{Entity} _{entity} = new {Entity}.{Entity}();
|
||||
|
||||
public static I{Entity} {Entity}
|
||||
{
|
||||
get => _instance._{entity};
|
||||
}
|
||||
```
|
||||
|
||||
## Examples from Codebase
|
||||
|
||||
### Example 1: DbAPI Facade
|
||||
**File:** `DataPRO/DbAPI/DbAPI.cs:23`
|
||||
```csharp
|
||||
public class DbAPI
|
||||
{
|
||||
private static readonly DbAPI _instance = new DbAPI();
|
||||
private readonly ConnectionManager _connectionManager = new ConnectionManager();
|
||||
|
||||
public static IConnections Connections => _instance._connectionManager;
|
||||
|
||||
private readonly Sensors.Sensors _sensors = new Sensors.Sensors();
|
||||
public static ISensors Sensors => _instance._sensors;
|
||||
|
||||
private readonly TestSetups.TestSetups _testSetups = new TestSetups.TestSetups();
|
||||
public static ITestSetups TestSetups => _instance._testSetups;
|
||||
|
||||
private readonly Groups.Groups _groups = new Groups.Groups();
|
||||
public static IGroups Groups => _instance._groups;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Sensor Data Access
|
||||
**File:** `DataPRO/DbAPI/Sensors/Sensors.cs:26`
|
||||
```csharp
|
||||
internal class Sensors : ISensors
|
||||
{
|
||||
public ulong SensorsAnalogDiagnosticsGet(IUserDbRecord user, IConnectionDetails connection,
|
||||
long? Id, long? diagnosticRunId, int? sensorId, string sensorSerialNumber,
|
||||
out IDiagnosticEntry[] records)
|
||||
{
|
||||
records = new IDiagnosticEntry[0];
|
||||
var list = new List<IDiagnosticEntry>();
|
||||
|
||||
if (!DbAPI.Connections.IsUserLoggedIn(user, connection))
|
||||
return ErrorCodes.ERROR_ACCESS_DENIED;
|
||||
|
||||
var ret = ConnectionManager.GetSqlCommand(connection, out var cmd, "sp_AnalogDiagnosticsGet");
|
||||
if (ret != ErrorCodes.ERROR_SUCCESS) return ret;
|
||||
|
||||
try
|
||||
{
|
||||
AddNullableBigIntParameter(cmd, "@Id", Id);
|
||||
AddNullableIntParameter(cmd, "@SensorId", sensorId);
|
||||
|
||||
var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
list.Add(new DiagnosticEntry(reader));
|
||||
}
|
||||
records = list.ToArray();
|
||||
return ErrorCodes.ERROR_SUCCESS;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogManager.Log(TraceEventType.Error, LogManager.LogEvents.Sensors,
|
||||
$"sp_AnalogDiagnosticsGet failed, {ex.Message}");
|
||||
return ErrorCodes.ERROR_UNKNOWN;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmd.Connection.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Connection Manager Usage
|
||||
**File:** `DataPRO/DbAPI/DbAPI.cs:104`
|
||||
```csharp
|
||||
public static ulong GetDatabaseVersion(IConnectionDetails connection, out int serverDbVersion)
|
||||
{
|
||||
serverDbVersion = 0;
|
||||
var ret = ConnectionManager.GetSqlCommand(connection, out var cmd, "sp_DbVersionGet");
|
||||
if (ret != ErrorCodes.ERROR_SUCCESS) return ret;
|
||||
|
||||
try
|
||||
{
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
var reader = cmd.ExecuteReader();
|
||||
var dbVersionsList = new List<int>();
|
||||
while (reader.Read())
|
||||
{
|
||||
var version = Convert.ToInt32(reader["Version"]);
|
||||
dbVersionsList.Add(version);
|
||||
}
|
||||
serverDbVersion = dbVersionsList.Max();
|
||||
return ErrorCodes.ERROR_SUCCESS;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cmd.Connection.Dispose();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Stored Procedure Version Check
|
||||
**File:** `DataPRO/DbAPI/DbAPI.cs:57`
|
||||
```csharp
|
||||
public static ulong GetStoredProcedureToUseCached(IConnectionDetails connection,
|
||||
string storedProcedure, int clientDbVersion, out int storedProcedureVersionToUse)
|
||||
{
|
||||
lock (StoredProcedureLock)
|
||||
{
|
||||
if (_spLookup.ContainsKey(storedProcedure))
|
||||
{
|
||||
var match = _spLookup[storedProcedure].Find(sp =>
|
||||
sp.ClientVersion == clientDbVersion && sp.DbVersion == connection.ConnectionDbVersion);
|
||||
if (null != match)
|
||||
{
|
||||
storedProcedureVersionToUse = match.StoredProcedureVersion;
|
||||
return ErrorCodes.ERROR_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
// ... determine version and cache it
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
1. **Not disposing SqlCommand connection** - Memory leak; always use `finally { cmd.Connection.Dispose(); }`
|
||||
2. **Missing authentication check** - Always verify `IsUserLoggedIn(user, connection)` first
|
||||
3. **Hardcoded stored procedure names** - Consider versioning for DB migrations
|
||||
4. **Not handling DBNull** - Use nullable parameters with DBNull.Value conversion
|
||||
5. **Swallowing exceptions** - Always log errors before returning error code
|
||||
6. **Using dynamic SQL** - Always use parameterized stored procedures
|
||||
7. **Not checking DB version** - Some features require minimum DB version
|
||||
8. **Returning null instead of empty array** - Initialize arrays as `new T[0]`
|
||||
9. **Missing error output parameter** - Some SPs require `@ErrorNumber` output
|
||||
10. **Not caching SP version lookups** - Use `GetStoredProcedureToUseCached()` for performance
|
||||
434
GLM5Analysis/PatternLibrary/ImportExportPattern.md
Normal file
434
GLM5Analysis/PatternLibrary/ImportExportPattern.md
Normal file
@@ -0,0 +1,434 @@
|
||||
# Import/Export Pattern
|
||||
|
||||
## When to Use
|
||||
- Importing sensor data from external files (CSV, XML, SIF, etc.)
|
||||
- Importing test setups and group configurations
|
||||
- Exporting data to external formats
|
||||
- Migrating data between database versions
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────────────────────────────┐
|
||||
│ Import Wizard Flow │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Options │───>│ Preview │───>│ Import │ │
|
||||
│ │ View │ │ View │ │ View │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ v v v │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ Shared Import ViewModel │ │
|
||||
│ │ - SourceFiles │ │
|
||||
│ │ - ParseSourceFiles() │ │
|
||||
│ │ - Validate() │ │
|
||||
│ │ - Import() │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ v │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ EventAggregator Events │ │
|
||||
│ │ - TTSImportReadFileFinishedEvent │ │
|
||||
│ │ - TTSImportTestSetupChangedEvent │ │
|
||||
│ │ - CustomChannelImportEvent │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
└────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
### Structure
|
||||
```
|
||||
Modules/{Module}/
|
||||
├── {Module}Import/
|
||||
│ ├── {Module}ImportModule.cs
|
||||
│ ├── View/
|
||||
│ │ ├── {Module}ImportOptionsView.xaml
|
||||
│ │ ├── {Module}ImportPreviewView.xaml
|
||||
│ │ └── {Module}ImportImportView.xaml
|
||||
│ └── ViewModel/
|
||||
│ └── {Module}ImportViewModel.cs
|
||||
|
||||
Common/DTS.CommonCore/
|
||||
├── Events/{Module}Import/
|
||||
│ └── {Event}Events.cs
|
||||
└── Interface/{Module}/
|
||||
└── I{Module}ImportViewModel.cs
|
||||
```
|
||||
|
||||
## Code Templates
|
||||
|
||||
### 1. Import ViewModel
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using Unity;
|
||||
using DTS.Common.Interface.{Module};
|
||||
using DTS.Common.Interactivity;
|
||||
using DTS.Common.Events;
|
||||
|
||||
namespace {Module}Import
|
||||
{
|
||||
[Export(typeof(I{Module}ImportOptionsView))]
|
||||
[Export(typeof(I{Module}ImportPreviewView))]
|
||||
[Export(typeof(I{Module}ImportImportView))]
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class {Module}ImportViewModel : I{Module}ImportViewModel
|
||||
{
|
||||
#region Views
|
||||
public I{Module}ImportOptionsView ImportOptionsView { get; set; }
|
||||
public I{Module}ImportPreviewView ImportPreviewView { get; set; }
|
||||
public I{Module}ImportImportView ImportView { get; set; }
|
||||
#endregion
|
||||
|
||||
private IEventAggregator _eventAggregator { get; set; }
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; set; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; private set; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; private set; }
|
||||
|
||||
#region Properties
|
||||
public string[] SourceFiles { get; set; }
|
||||
public List<ImportItem> ParsedItems { get; set; }
|
||||
public List<ImportError> Errors { get; set; }
|
||||
public bool HasErrors => Errors?.Any() == true;
|
||||
#endregion
|
||||
|
||||
public {Module}ImportViewModel(
|
||||
I{Module}ImportOptionsView optionsView,
|
||||
I{Module}ImportPreviewView previewView,
|
||||
I{Module}ImportImportView importView,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
ImportOptionsView = optionsView;
|
||||
ImportOptionsView.DataContext = this;
|
||||
ImportPreviewView = previewView;
|
||||
ImportPreviewView.DataContext = this;
|
||||
ImportView = importView;
|
||||
ImportView.DataContext = this;
|
||||
|
||||
NotificationRequest = new InteractionRequest<Notification>();
|
||||
ConfirmationRequest = new InteractionRequest<Confirmation>();
|
||||
|
||||
_eventAggregator = eventAggregator;
|
||||
UnityContainer = unityContainer;
|
||||
_regionManager = regionManager;
|
||||
|
||||
_eventAggregator.GetEvent<RaiseNotification>().Subscribe(OnRaiseNotification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse source files and extract data
|
||||
/// </summary>
|
||||
public void ParseSourceFiles()
|
||||
{
|
||||
ParsedItems = new List<ImportItem>();
|
||||
Errors = new List<ImportError>();
|
||||
|
||||
foreach (var file in SourceFiles ?? new string[0])
|
||||
{
|
||||
try
|
||||
{
|
||||
var lines = File.ReadAllLines(file);
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i];
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
var item = ParseLine(line, file, i);
|
||||
if (item != null)
|
||||
{
|
||||
ParsedItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Errors.Add(new ImportError
|
||||
{
|
||||
File = file,
|
||||
Message = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
OnPropertyChanged("ParsedItems");
|
||||
OnPropertyChanged("HasErrors");
|
||||
}
|
||||
|
||||
private ImportItem ParseLine(string line, string file, int lineNumber)
|
||||
{
|
||||
var fields = line.Split(',');
|
||||
|
||||
// Validate required fields
|
||||
if (fields.Length < 3)
|
||||
{
|
||||
Errors.Add(new ImportError
|
||||
{
|
||||
File = file,
|
||||
Line = lineNumber,
|
||||
Message = "Insufficient fields"
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ImportItem
|
||||
{
|
||||
Name = fields[0],
|
||||
Value = fields[1],
|
||||
Type = fields[2]
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the import
|
||||
/// </summary>
|
||||
public void Import()
|
||||
{
|
||||
var successCount = 0;
|
||||
var errors = new List<string>();
|
||||
|
||||
foreach (var item in ParsedItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Call DbAPI to insert
|
||||
var result = DbAPI.DbAPI.{Entity}.{Entity}Add(
|
||||
CurrentUser.User,
|
||||
ConnectionManager.CurrentConnection,
|
||||
item.ToRecord(),
|
||||
out int newId);
|
||||
|
||||
if (result == ErrorCodes.ERROR_SUCCESS)
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.Add($"Failed to import {item.Name}: Error {result}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add($"Exception importing {item.Name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.Any())
|
||||
{
|
||||
NotificationRequest.Raise(new Notification
|
||||
{
|
||||
Title = "Import Completed with Errors",
|
||||
Content = $"{successCount} items imported successfully.\n{errors.Count} errors occurred."
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
NotificationRequest.Raise(new Notification
|
||||
{
|
||||
Title = "Import Successful",
|
||||
Content = $"{successCount} items imported successfully."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public void OnPropertyChanged(string propertyName) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
|
||||
// IBaseViewModel implementation...
|
||||
public bool IsBusy { get; set; }
|
||||
public bool IsDirty => false;
|
||||
public bool IsMenuIncluded { get; set; }
|
||||
public bool IsNavigationIncluded { get; set; }
|
||||
public void Initialize() { }
|
||||
public void Initialize(object parameter) { }
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
public Task InitializeAsync(object parameter) => Task.CompletedTask;
|
||||
public void Activated() { }
|
||||
public void Cleanup() { }
|
||||
public Task CleanupAsync() => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Import Event Definitions
|
||||
**File:** `Common/DTS.CommonCore/Events/{Module}Import/{Event}Event.cs`
|
||||
```csharp
|
||||
using Prism.Events;
|
||||
using DTS.Common.Base;
|
||||
|
||||
namespace DTS.Common.Events.{Module}Import
|
||||
{
|
||||
/// <summary>
|
||||
/// Event fired when import file parsing is complete
|
||||
/// </summary>
|
||||
public class {Module}ImportFinishedEvent : PubSubEvent<IImportData> { }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an item is imported
|
||||
/// </summary>
|
||||
public class {Module}ItemImportedEvent : PubSubEvent<ImportedItemEventArgs> { }
|
||||
|
||||
public class ImportedItemEventArgs
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int NewId { get; set; }
|
||||
public bool Success { get; set; }
|
||||
public string Error { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. File Parser Pattern
|
||||
```csharp
|
||||
namespace DTS.Common.Import.Parsers
|
||||
{
|
||||
public interface IParseImport
|
||||
{
|
||||
string[] SupportedExtensions { get; }
|
||||
bool CanParse(string filePath);
|
||||
IImportData Parse(string filePath);
|
||||
}
|
||||
|
||||
public class CSVParser : IParseImport
|
||||
{
|
||||
public string[] SupportedExtensions => new[] { ".csv" };
|
||||
|
||||
public bool CanParse(string filePath) =>
|
||||
Path.GetExtension(filePath).Equals(".csv", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public IImportData Parse(string filePath)
|
||||
{
|
||||
var data = new ImportData();
|
||||
var lines = File.ReadAllLines(filePath);
|
||||
|
||||
// Skip header if present
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
{
|
||||
var fields = lines[i].Split(',');
|
||||
// Parse fields...
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples from Codebase
|
||||
|
||||
### Example 1: GroupImportViewModel
|
||||
**File:** `DataPRO/Modules/Groups/GroupImport/ViewModel/GroupImportViewModel.cs:36`
|
||||
```csharp
|
||||
[Export(typeof(IGroupImportOptionsView))]
|
||||
[Export(typeof(IGroupImportPreviewView))]
|
||||
[Export(typeof(IGroupImportImportView))]
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class GroupImportViewModel : IGroupImportViewModel
|
||||
{
|
||||
public IGroupImportOptionsView ImportOptionsView { get; set; }
|
||||
public IGroupImportPreviewView ImportPreviewView { get; set; }
|
||||
public IGroupImportImportView ImportView { get; set; }
|
||||
|
||||
public GroupImportViewModel(
|
||||
IGroupImportOptionsView optionsView,
|
||||
IGroupImportPreviewView previewView,
|
||||
IGroupImportImportView importView,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
ImportView = importView;
|
||||
ImportView.DataContext = this;
|
||||
// ...
|
||||
}
|
||||
|
||||
public void ParseSourceFiles(string userTags)
|
||||
{
|
||||
var groups = new List<GroupGRPImportGroup>();
|
||||
foreach (var file in SourceFiles)
|
||||
{
|
||||
var lines = System.IO.File.ReadAllLines(file);
|
||||
// Parse group data...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: TTS Import Events
|
||||
**File:** `Common/DTS.CommonCore/Events/TTSImport/TTSImportReadFileFinishedEvent.cs:13`
|
||||
```csharp
|
||||
public class TTSImportReadFileFinishedEvent : CompositePresentationEvent<ITTSSetup> { }
|
||||
```
|
||||
|
||||
**File:** `Common/DTS.CommonCore/Events/TTSImport/TTSImportTestSetupChangedEvent.cs:13`
|
||||
```csharp
|
||||
public class TTSImportTestSetupChangedEvent : CompositePresentationEvent<ITTSSetup> { }
|
||||
```
|
||||
|
||||
### Example 3: Sensor Importer Base Class
|
||||
**File:** `DataPRO/DataPRO/Controls/Sensors and models/Classes/SensorTestSetupImporter.cs:40`
|
||||
```csharp
|
||||
public abstract class SensorTestSetupImporter : BasePropertyChanged, ISensorTestSetupImporter
|
||||
{
|
||||
public abstract string[] SupportedExtensions { get; }
|
||||
public abstract string FileFilter { get; }
|
||||
|
||||
public abstract ISensorData[] ParseImport(string[] files, ImportOptions options);
|
||||
public abstract void ImportSensors(ISensorData[] sensors, IConnectionDetails connection);
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: CSVImporter Implementation
|
||||
**File:** `DataPRO/DataPRO/Controls/Sensors and models/Classes/CSVImporter.cs:30`
|
||||
```csharp
|
||||
public class CSVImporter : SensorTestSetupImporter
|
||||
{
|
||||
public override string[] SupportedExtensions => new[] { ".csv" };
|
||||
public override string FileFilter => "CSV Files (*.csv)|*.csv";
|
||||
|
||||
public override ISensorData[] ParseImport(string[] files, ImportOptions options)
|
||||
{
|
||||
var sensors = new List<ISensorData>();
|
||||
foreach (var file in files)
|
||||
{
|
||||
var lines = File.ReadAllLines(file);
|
||||
for (int i = 1; i < lines.Length; i++) // Skip header
|
||||
{
|
||||
var fields = lines[i].Split(',');
|
||||
sensors.Add(new SensorData
|
||||
{
|
||||
SerialNumber = fields[0],
|
||||
Description = fields[1],
|
||||
// ...
|
||||
});
|
||||
}
|
||||
}
|
||||
return sensors.ToArray();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
1. **Not validating before import** - Always parse and preview before executing import
|
||||
2. **Missing error collection** - Track errors per line/item, not just global exceptions
|
||||
3. **Blocking UI during import** - Use async/await or background threads
|
||||
4. **Not handling file encoding** - Use proper encoding (UTF-8, ANSI) for international files
|
||||
5. **Hardcoded column indexes** - Use header row to find columns by name
|
||||
6. **Not reporting progress** - For large imports, report progress percentage
|
||||
7. **Missing transaction rollback** - Consider wrapping imports in transactions
|
||||
8. **Not checking for duplicates** - Validate against existing database records
|
||||
9. **Silent failures** - Always notify user of failed imports with specific error messages
|
||||
10. **Not supporting cancel** - Allow user to cancel long-running imports
|
||||
275
GLM5Analysis/PatternLibrary/MVVM_Pattern.md
Normal file
275
GLM5Analysis/PatternLibrary/MVVM_Pattern.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# MVVM Pattern
|
||||
|
||||
## When to Use
|
||||
- All UI development in DataPRO
|
||||
- Creating views that need to be testable
|
||||
- Separating business logic from presentation
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
### Standard Structure
|
||||
```
|
||||
{Module}/
|
||||
├── Interface/
|
||||
│ ├── I{ViewModelName}.cs (in DTS.Common or DTS.CommonCore)
|
||||
│ └── I{ViewName}.cs (in DTS.Common or DTS.CommonCore)
|
||||
├── View/
|
||||
│ ├── {ViewName}.xaml
|
||||
│ └── {ViewName}.xaml.cs
|
||||
└── ViewModel/
|
||||
└── {ViewModelName}.cs
|
||||
```
|
||||
|
||||
## Code Templates
|
||||
|
||||
### 1. View Interface
|
||||
**File:** `DTS.CommonCore/Interface/{Module}/I{ViewName}.cs`
|
||||
```csharp
|
||||
using DTS.Common.Base;
|
||||
|
||||
namespace DTS.Common.Interface.{Module}
|
||||
{
|
||||
public interface I{ViewName} : IBaseView
|
||||
{
|
||||
// Add view-specific properties/methods if needed
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. ViewModel Interface
|
||||
**File:** `DTS.CommonCore/Interface/{Module}/I{ViewModelName}.cs`
|
||||
```csharp
|
||||
using DTS.Common.Base;
|
||||
|
||||
namespace DTS.Common.Interface.{Module}
|
||||
{
|
||||
public interface I{ViewModelName} : IBaseViewModel
|
||||
{
|
||||
I{ViewName} View { get; set; }
|
||||
// Add other properties/methods
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. ViewModel Implementation
|
||||
```csharp
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using Unity;
|
||||
using DTS.Common.Interface.{Module};
|
||||
using DTS.Common.Interactivity;
|
||||
using DTS.Common.Events;
|
||||
|
||||
namespace {ModuleName}
|
||||
{
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class {ViewModelName} : I{ViewModelName}
|
||||
{
|
||||
public I{ViewName} View { get; set; }
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private readonly IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public InteractionRequest<Notification> NotificationRequest { get; }
|
||||
public InteractionRequest<Confirmation> ConfirmationRequest { get; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public {ViewModelName}(
|
||||
I{ViewName} view,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
View.DataContext = this;
|
||||
|
||||
NotificationRequest = new InteractionRequest<Notification>();
|
||||
ConfirmationRequest = new InteractionRequest<Confirmation>();
|
||||
|
||||
_eventAggregator = eventAggregator;
|
||||
UnityContainer = unityContainer;
|
||||
_regionManager = regionManager;
|
||||
|
||||
_eventAggregator.GetEvent<RaiseNotification>().Subscribe(OnRaiseNotification);
|
||||
_eventAggregator.GetEvent<BusyIndicatorChangeNotification>()
|
||||
.Subscribe(OnBusyIndicatorNotification, ThreadOption.PublisherThread, true);
|
||||
}
|
||||
|
||||
private bool _isBusy = false;
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
set { _isBusy = value; OnPropertyChanged("IsBusy"); }
|
||||
}
|
||||
|
||||
public bool IsDirty { get; private set; }
|
||||
|
||||
public bool IsMenuIncluded { get; set; }
|
||||
public bool IsNavigationIncluded { get; set; }
|
||||
|
||||
public void Initialize() { }
|
||||
public void Initialize(object parameter) { }
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
public Task InitializeAsync(object parameter) => Task.CompletedTask;
|
||||
public void Activated() { }
|
||||
public void Cleanup() { }
|
||||
public Task CleanupAsync() => Task.CompletedTask;
|
||||
|
||||
private void OnBusyIndicatorNotification(bool eventArg) => IsBusy = eventArg;
|
||||
|
||||
private void OnRaiseNotification(NotificationContentEventArgs eventArgs)
|
||||
{
|
||||
NotificationRequest.Raise(new Notification {
|
||||
Content = eventArgs,
|
||||
Title = eventArgs.Title
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. XAML View
|
||||
```xml
|
||||
<base:BaseView x:Class="{ModuleName}.{ViewName}"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:base="clr-namespace:DTS.Common.Base;assembly=DTS.Common"
|
||||
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
|
||||
xmlns:converters="clr-namespace:DTS.Common.Converters;assembly=DTS.Common">
|
||||
|
||||
<base:BaseView.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/DTS.Common;component/Themes/CommonStyles.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibility" />
|
||||
</ResourceDictionary>
|
||||
</base:BaseView.Resources>
|
||||
|
||||
<Grid>
|
||||
<!-- Controls bound to ViewModel properties -->
|
||||
<ListView ItemsSource="{Binding Items}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
|
||||
<ListView.ItemContainerStyle>
|
||||
<Style TargetType="ListViewItem" BasedOn="{StaticResource TTS_ListViewItemStyle}">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Status}" Value="Error">
|
||||
<Setter Property="Background" Value="{StaticResource Brush_ApplicationStatus_Failed}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ListView.ItemContainerStyle>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</base:BaseView>
|
||||
```
|
||||
|
||||
### 5. View Code-Behind
|
||||
```csharp
|
||||
using System.Windows.Controls;
|
||||
using DTS.Common.Base;
|
||||
using DTS.Common.Interface.{Module};
|
||||
|
||||
namespace {ModuleName}
|
||||
{
|
||||
public partial class {ViewName} : BaseView, I{ViewName}
|
||||
{
|
||||
public {ViewName}()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples from Codebase
|
||||
|
||||
### Example 1: SensorsListViewModel
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/ViewModel/SensorsListViewModel.cs:41`
|
||||
```csharp
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class SensorsListViewModel : ISensorsListViewModel
|
||||
{
|
||||
public ISensorsListView View { get; set; }
|
||||
private IEventAggregator _eventAggregator { get; }
|
||||
private readonly IRegionManager _regionManager;
|
||||
private IUnityContainer UnityContainer { get; }
|
||||
|
||||
public SensorsListViewModel(
|
||||
ISensorsListView view,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
View.DataContext = this;
|
||||
_eventAggregator = eventAggregator;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: ShellViewModel (BindableBase)
|
||||
**File:** `DataPRO/DataPRO/ViewModel/ShellViewModel.cs:26`
|
||||
```csharp
|
||||
[Export(typeof(IShellView))]
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class ShellViewModel : BindableBase, IShellViewModel
|
||||
{
|
||||
public IShellView View { get; private set; }
|
||||
|
||||
public ShellViewModel(
|
||||
IShellView view,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
View.DataContext = this;
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: ISensorsListViewModel Interface
|
||||
**File:** `Common/DTS.CommonCore/Interface/Sensors/SensorsList/ISensorsListViewModel.cs:8`
|
||||
```csharp
|
||||
public interface ISensorsListViewModel : IBaseViewModel, IFilterableListView
|
||||
{
|
||||
ISensorsListView View { get; set; }
|
||||
IAnalogSensor[] AnalogSensors { get; set; }
|
||||
void GetSensors(int sensorCalWarningPeriodDays, bool included);
|
||||
void Filter(string currentFilter);
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: XAML Binding Pattern
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/View/SensorsListView.xaml:44`
|
||||
```xml
|
||||
<ListView ItemsSource="{Binding AnalogSensors, UpdateSourceTrigger=PropertyChanged}"
|
||||
SelectedIndex="{Binding SelectedAnalogIndex, Mode=TwoWay}">
|
||||
<i:Interaction.Behaviors>
|
||||
<behaviors:MultiSelectionBehavior SelectedItems="{Binding SelectedAnalogItems}" />
|
||||
</i:Interaction.Behaviors>
|
||||
</ListView>
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
1. **Not setting DataContext in constructor** - View won't bind to ViewModel
|
||||
2. **Forgetting INotifyPropertyChanged** - UI won't update when properties change
|
||||
3. **Using concrete types instead of interfaces** - Breaks DI and testability
|
||||
4. **Putting logic in code-behind** - Should be in ViewModel
|
||||
5. **Not using InteractionRequest for dialogs** - Use `NotificationRequest.Raise()` instead of `MessageBox.Show()`
|
||||
6. **Hardcoded strings in OnPropertyChanged** - Use `nameof()` when possible
|
||||
7. **Not implementing IBaseViewModel fully** - Missing Initialize/Cleanup methods
|
||||
8. **Blocking UI thread** - Use async/await for long operations
|
||||
187
GLM5Analysis/PatternLibrary/PrismModulePattern.md
Normal file
187
GLM5Analysis/PatternLibrary/PrismModulePattern.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Prism Module Pattern
|
||||
|
||||
## When to Use
|
||||
- Creating a new feature module that integrates with DataPRO shell
|
||||
- Adding new UI components that need to be loaded at runtime
|
||||
- Implementing isolated feature sets that can be developed independently
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
### 1. Module Project Structure
|
||||
```
|
||||
DataPRO/Modules/{ModuleName}/
|
||||
├── {ModuleName}Module.cs (Module entry point)
|
||||
├── View/
|
||||
│ └── {ViewName}.xaml(.cs) (XAML views)
|
||||
├── ViewModel/
|
||||
│ └── {ViewModelName}.cs (View models)
|
||||
├── Model/ (Data models, if needed)
|
||||
└── Resources/ (String resources, images)
|
||||
```
|
||||
|
||||
### 2. Register Module in Bootstrapper
|
||||
**File:** `DataPRO/DataPRO/Bootstrapper.cs`
|
||||
Add to `ConfigureModuleCatalog()`:
|
||||
```csharp
|
||||
moduleCatalog.AddModule<{ModuleName}.{ModuleClass}>();
|
||||
```
|
||||
|
||||
## Code Template
|
||||
|
||||
### Module Class (`{ModuleName}Module.cs`)
|
||||
```csharp
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Media.Imaging;
|
||||
using DTS.Common;
|
||||
using DTS.Common.Interface;
|
||||
using Prism.Ioc;
|
||||
using Prism.Modularity;
|
||||
using Unity;
|
||||
|
||||
[assembly: {ModuleName}Name]
|
||||
[assembly: {ModuleName}ImageAttribute]
|
||||
namespace {ModuleName}
|
||||
{
|
||||
[Export(typeof(IModule))]
|
||||
[Module(ModuleName = "{ModuleName}")]
|
||||
public class {ModuleName}Module : IModule
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public {ModuleName}Module(IUnityContainer unityContainer)
|
||||
{
|
||||
_unityContainer = unityContainer;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_unityContainer.RegisterType<I{ViewName}, {ViewName}>();
|
||||
_unityContainer.RegisterType<I{ViewModelName}, {ViewModelName}>();
|
||||
}
|
||||
|
||||
public void OnInitialized(IContainerProvider containerProvider) { }
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
|
||||
public class {ModuleName}NameAttribute : TextAttribute
|
||||
{
|
||||
public {ModuleName}NameAttribute() : this(null) { }
|
||||
public {ModuleName}NameAttribute(string s)
|
||||
{
|
||||
AssemblyName = AssemblyNames.{ModuleName}.ToString();
|
||||
}
|
||||
public override string AssemblyName { get; }
|
||||
public override Type GetAttributeType() => typeof(TextAttribute);
|
||||
public override string GetAssemblyName() => AssemblyName;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
|
||||
public class {ModuleName}ImageAttribute : ImageAttribute
|
||||
{
|
||||
private BitmapImage _img;
|
||||
public {ModuleName}ImageAttribute() : this(null) { }
|
||||
public override BitmapImage AssemblyImage
|
||||
{
|
||||
get { _img = AssemblyInfo.GetImage(AssemblyNames.{ModuleName}.ToString()); return _img; }
|
||||
}
|
||||
public {ModuleName}ImageAttribute(string s)
|
||||
{
|
||||
_img = AssemblyInfo.GetImage(AssemblyNames.{ModuleName}.ToString());
|
||||
}
|
||||
public override Type GetAttributeType() => typeof(ImageAttribute);
|
||||
public override BitmapImage GetAssemblyImage() => AssemblyImage;
|
||||
private string _name;
|
||||
public override string AssemblyName
|
||||
{
|
||||
get { _name = AssemblyNames.{ModuleName}.ToString(); return _name; }
|
||||
}
|
||||
public override string GetAssemblyName() => AssemblyName;
|
||||
private string _group;
|
||||
public override string AssemblyGroup
|
||||
{
|
||||
get { _group = eAssemblyGroups.{GroupType}.ToString(); return _group; }
|
||||
}
|
||||
public override string GetAssemblyGroup() => AssemblyGroup;
|
||||
private eAssemblyRegion _region;
|
||||
public override eAssemblyRegion AssemblyRegion
|
||||
{
|
||||
get { _region = eAssemblyRegion.{ModuleName}Region; return _region; }
|
||||
}
|
||||
public override eAssemblyRegion GetAssemblyRegion() => AssemblyRegion;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples from Codebase
|
||||
|
||||
### Example 1: SensorsListModule
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/SensorsListModule.cs:22`
|
||||
```csharp
|
||||
[Module(ModuleName = "SensorsListModule")]
|
||||
public class SensorsListModule : IModule
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public SensorsListModule(IUnityContainer unityContainer)
|
||||
{
|
||||
_unityContainer = unityContainer;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_unityContainer.RegisterType<ISensorsListView, SensorsListView>();
|
||||
_unityContainer.RegisterType<ISensorsListViewModel, SensorsListViewModel>();
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry) => Initialize();
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: GraphModule (DTS Viewer)
|
||||
**File:** `DTS Viewer/DTS.Viewer.Modules/DTS.Viewer.Graph/GraphModule.cs:16`
|
||||
```csharp
|
||||
[Module(ModuleName = "Graph")]
|
||||
public class GraphModule : IModule
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public GraphModule(IUnityContainer unityContainer)
|
||||
{
|
||||
_unityContainer = unityContainer;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_unityContainer.RegisterType<IGraphView, GraphView>();
|
||||
_unityContainer.RegisterType<IGraphViewModel, GraphViewModel>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Bootstrapper Registration
|
||||
**File:** `DataPRO/DataPRO/Bootstrapper.cs:179-220`
|
||||
```csharp
|
||||
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
|
||||
{
|
||||
moduleCatalog.AddModule<StatusAndProgressBar.StatusAndProgressBarModule>();
|
||||
moduleCatalog.AddModule<DatabaseServices.DatabaseServicesModule>();
|
||||
moduleCatalog.AddModule<SensorsList.SensorsListModule>();
|
||||
// ... more modules
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
1. **Forgetting to register module in Bootstrapper** - Module won't load
|
||||
2. **Not implementing both `Initialize()` and `RegisterTypes()`** - Both are required for Prism 6+
|
||||
3. **Missing assembly attributes** - Module won't appear in UI tiles/menu
|
||||
4. **Wrong eAssemblyGroups value** - Module appears in wrong section
|
||||
5. **Not using interface for View/ViewModel registration** - Breaks testability and DI
|
||||
6. **Singleton vs Transient** - Use `ContainerControlledLifetimeManager` for singletons, otherwise default is transient
|
||||
7. **Missing `[Export(typeof(IModule))]`** - Required for MEF discovery in some scenarios
|
||||
376
GLM5Analysis/PatternLibrary/ServicePattern.md
Normal file
376
GLM5Analysis/PatternLibrary/ServicePattern.md
Normal file
@@ -0,0 +1,376 @@
|
||||
# Service Pattern (Background Services)
|
||||
|
||||
## When to Use
|
||||
- Implementing hardware communication (SLICE, TDAS, Ribeye)
|
||||
- Long-running background operations
|
||||
- Real-time data acquisition
|
||||
- Download operations from hardware devices
|
||||
- Diagnostics and calibration services
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────┐
|
||||
│ ServiceBase │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Callback Pattern: ServiceCallback(CallbackData) │ │
|
||||
│ │ - Progress: Report progress percentage │ │
|
||||
│ │ - NewData: Real-time sample data │ │
|
||||
│ │ - Success/Failure: Operation completion │ │
|
||||
│ │ - Canceled: User cancellation │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ State Machine: Stateless-based state transitions │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
|
||||
│ │ Prepare │───>│Configure│───>│ Arm │───>│ Realtime │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └──────────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ v v v v │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
|
||||
│ │ Diagnose│ │ Download│ │ Disarm │ │ Stop │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └──────────┘ │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Create/Modify
|
||||
|
||||
### Structure
|
||||
```
|
||||
IService/
|
||||
├── Classes/
|
||||
│ ├── ServiceBase.cs (Base class for all services)
|
||||
│ ├── GenericServices.cs (Service orchestration)
|
||||
│ ├── SLICEService/
|
||||
│ │ ├── SLICE Service.cs
|
||||
│ │ ├── SLICE Service.Configuration.cs
|
||||
│ │ ├── SLICE Service.Realtime.cs
|
||||
│ │ └── SLICE Service.Download.cs
|
||||
│ └── TDAS Service/
|
||||
├── Interfaces/
|
||||
│ ├── IConfigurationActions.cs
|
||||
│ ├── IRealTimeActions.cs
|
||||
│ ├── IDownloadActions.cs
|
||||
│ └── IDiagnosticsActions.cs
|
||||
└── StateMachine/
|
||||
├── States.cs
|
||||
├── Triggers.cs
|
||||
└── States/
|
||||
├── Configure.cs
|
||||
├── Realtime.cs
|
||||
└── Download.cs
|
||||
```
|
||||
|
||||
## Code Templates
|
||||
|
||||
### 1. Service Base Class Pattern
|
||||
```csharp
|
||||
using System;
|
||||
using System.Threading;
|
||||
using DTS.Common.Utilities.Logging;
|
||||
|
||||
namespace DTS.DASLib.Service
|
||||
{
|
||||
public abstract class ServiceBase : IDisposable
|
||||
{
|
||||
public bool AggregateProgress { get; set; }
|
||||
|
||||
public class CallbackData
|
||||
{
|
||||
public enum CallbackStatus
|
||||
{
|
||||
Progress,
|
||||
NewData,
|
||||
AllFinished,
|
||||
Success,
|
||||
Failure,
|
||||
Canceled
|
||||
}
|
||||
|
||||
public CallbackStatus Status { get; set; }
|
||||
public IDASCommunication Target { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public Exception ErrorException { get; set; }
|
||||
public int ProgressValue { get; set; }
|
||||
public SampleData[] Data { get; set; }
|
||||
}
|
||||
|
||||
public delegate void Callback(CallbackData data);
|
||||
public delegate void ServiceBaseEventHandler(object sender, CallbackData data);
|
||||
|
||||
public event ServiceBaseEventHandler ServiceAvailable;
|
||||
public event ServiceCallbackErrorEventHandler ServiceCallbackError;
|
||||
|
||||
public abstract string ServiceName();
|
||||
|
||||
public virtual void Cancel()
|
||||
{
|
||||
APILogger.LogString($"Entering {ServiceName()}.Cancel");
|
||||
DASServiceLock.Cancel(this);
|
||||
}
|
||||
|
||||
protected void RunService(Callback userCallback, object userObject)
|
||||
{
|
||||
var glue = new ServiceGlueClass(userCallback, userObject);
|
||||
// Service orchestration logic
|
||||
}
|
||||
|
||||
protected void FireCallback(CallbackData data)
|
||||
{
|
||||
// Fire callback to user
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Hardware Service Implementation
|
||||
```csharp
|
||||
using System;
|
||||
using System.Threading;
|
||||
using DTS.Common.DASResource;
|
||||
using DTS.Common.Interface.DASFactory;
|
||||
|
||||
namespace DTS.DASLib.Service
|
||||
{
|
||||
public class SLICEService : ServiceBase,
|
||||
IConfigurationActions,
|
||||
IRealTimeActions,
|
||||
IDownloadActions
|
||||
{
|
||||
public override string ServiceName() => "SLICE Service";
|
||||
|
||||
#region Configuration
|
||||
|
||||
public void Configure(
|
||||
IDASCommunication[] targets,
|
||||
IServiceConfiguration config,
|
||||
Callback callback,
|
||||
object userObject)
|
||||
{
|
||||
APILogger.LogString($"{ServiceName()}.Configure starting");
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var slice = target as ISLICE;
|
||||
if (slice == null) continue;
|
||||
|
||||
ApplyConfiguration(slice, config);
|
||||
}
|
||||
|
||||
callback?.Invoke(new CallbackData
|
||||
{
|
||||
Status = CallbackData.CallbackStatus.Success,
|
||||
Target = null
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
callback?.Invoke(new CallbackData
|
||||
{
|
||||
Status = CallbackData.CallbackStatus.Failure,
|
||||
ErrorException = ex,
|
||||
ErrorMessage = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Realtime
|
||||
|
||||
public void StartRealtime(
|
||||
IDASCommunication[] targets,
|
||||
Callback callback,
|
||||
object userObject)
|
||||
{
|
||||
APILogger.LogString($"{ServiceName()}.StartRealtime");
|
||||
|
||||
RunService(callback, userObject);
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var slice = target as ISLICE;
|
||||
slice?.StartRealtime(OnRealtimeData, target);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRealtimeData(SampleData[] data, object context)
|
||||
{
|
||||
FireCallback(new CallbackData
|
||||
{
|
||||
Status = CallbackData.CallbackStatus.NewData,
|
||||
Data = data,
|
||||
Target = context as IDASCommunication
|
||||
});
|
||||
}
|
||||
|
||||
public void StopRealtime(IDASCommunication[] targets)
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var slice = target as ISLICE;
|
||||
slice?.StopRealtime();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Download
|
||||
|
||||
public void Download(
|
||||
IDASCommunication[] targets,
|
||||
IDownloadParameters parameters,
|
||||
Callback callback,
|
||||
object userObject)
|
||||
{
|
||||
APILogger.LogString($"{ServiceName()}.Download");
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var slice = target as ISLICE;
|
||||
slice?.Download(
|
||||
parameters,
|
||||
(progress, data, error) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
callback?.Invoke(new CallbackData
|
||||
{
|
||||
Status = CallbackData.CallbackStatus.Failure,
|
||||
ErrorMessage = error,
|
||||
Target = target
|
||||
});
|
||||
}
|
||||
else if (data != null)
|
||||
{
|
||||
callback?.Invoke(new CallbackData
|
||||
{
|
||||
Status = CallbackData.CallbackStatus.NewData,
|
||||
Data = data,
|
||||
ProgressValue = progress,
|
||||
Target = target
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callback?.Invoke(new CallbackData
|
||||
{
|
||||
Status = CallbackData.CallbackStatus.AllFinished,
|
||||
Target = null
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Service Interface
|
||||
```csharp
|
||||
namespace DTS.DASLib.Service.Interfaces
|
||||
{
|
||||
public interface IRealTimeActions
|
||||
{
|
||||
void StartRealtime(IDASCommunication[] targets, ServiceBase.Callback callback, object userObject);
|
||||
void StopRealtime(IDASCommunication[] targets);
|
||||
}
|
||||
|
||||
public interface IDownloadActions
|
||||
{
|
||||
void Download(IDASCommunication[] targets, IDownloadParameters parameters,
|
||||
ServiceBase.Callback callback, object userObject);
|
||||
}
|
||||
|
||||
public interface IConfigurationActions
|
||||
{
|
||||
void Configure(IDASCommunication[] targets, IServiceConfiguration config,
|
||||
ServiceBase.Callback callback, object userObject);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples from Codebase
|
||||
|
||||
### Example 1: ServiceBase Callback Pattern
|
||||
**File:** `DataPRO/IService/Classes/GenericServices.cs:35-122`
|
||||
```csharp
|
||||
public abstract class ServiceBase : IDisposable
|
||||
{
|
||||
public class CallbackData
|
||||
{
|
||||
public enum CallbackStatus { Progress, NewData, AllFinished, Success, Failure, Canceled }
|
||||
|
||||
public CallbackStatus Status { get; set; }
|
||||
public IDASCommunication Target { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public Exception ErrorException { get; set; }
|
||||
public int ProgressValue { get; set; }
|
||||
public SampleData[] Data { get; set; }
|
||||
}
|
||||
|
||||
public delegate void Callback(CallbackData data);
|
||||
public event ServiceBaseEventHandler ServiceAvailable;
|
||||
|
||||
public abstract string ServiceName();
|
||||
|
||||
public virtual void Cancel()
|
||||
{
|
||||
APILogger.LogString($"Entering {ServiceName()}.Cancel");
|
||||
DASServiceLock.Cancel(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Service with Progress Reporting
|
||||
**File:** `DataPRO/IService/Classes/GenericServices.cs:198`
|
||||
```csharp
|
||||
protected void ServiceIsAvailable(object sender, CallbackData data)
|
||||
{
|
||||
ServiceAvailable -= ServiceIsAvailable;
|
||||
// Handle service completion
|
||||
}
|
||||
|
||||
protected class ServiceGlueClass
|
||||
{
|
||||
public object UserObject { get; set; }
|
||||
public Callback UserCallback { get; set; }
|
||||
public ManualResetEvent AvailableEvent { get; set; }
|
||||
|
||||
public ServiceGlueClass(Callback userCallback, object userObject)
|
||||
{
|
||||
UserObject = userObject;
|
||||
UserCallback = userCallback;
|
||||
AvailableEvent = new ManualResetEvent(false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: DASModule Configuration
|
||||
**File:** `DataPRO/IService/Classes/DASModule.cs`
|
||||
```csharp
|
||||
public class DASModule
|
||||
{
|
||||
public List<AnalogInputDASChannel> AnalogInputChannels { get; set; }
|
||||
public List<DigitalOutputDASChannel> DigitalOutputChannels { get; set; }
|
||||
|
||||
public void Configure(IServiceConfiguration config)
|
||||
{
|
||||
// Apply configuration to all channels
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
1. **Not handling cancellation** - Always implement `Cancel()` to clean up resources
|
||||
2. **Blocking the callback thread** - Use async patterns, don't block in callbacks
|
||||
3. **Missing error handling in callbacks** - Always check for null callback before invoking
|
||||
4. **Not disposing connections** - Implement `IDisposable` properly
|
||||
5. **Fire-and-forget without error logging** - Always log errors even if no callback
|
||||
6. **Cross-thread UI updates** - Use `Dispatcher.Invoke` for UI updates from service callbacks
|
||||
7. **Not using ServiceAvailable event** - Must unsubscribe to prevent memory leaks
|
||||
8. **Ignoring Target in CallbackData** - Needed to identify which unit responded
|
||||
9. **Not checking AggregateProgress** - Affects how progress is reported for multiple units
|
||||
10. **Missing ServiceName() override** - Required for logging identification
|
||||
Reference in New Issue
Block a user