init
This commit is contained in:
420
GLM5Analysis/Architecture.md
Normal file
420
GLM5Analysis/Architecture.md
Normal file
@@ -0,0 +1,420 @@
|
||||
# DataPRO Architecture Document
|
||||
|
||||
## System Overview
|
||||
|
||||
DataPRO is a WPF-based desktop application for data acquisition system (DAS) management and sensor data analysis. It provides:
|
||||
|
||||
- **Hardware Management**: Configuration and control of DTS data acquisition hardware (SLICE, TDAS, Ribeye devices)
|
||||
- **Sensor Configuration**: Management of sensor databases, calibrations, and channel setups
|
||||
- **Test Setup Management**: Creation and management of test configurations
|
||||
- **Real-time Data Collection**: Live streaming and visualization of sensor data
|
||||
- **Data Review & Analysis**: Post-test data visualization, filtering, and export
|
||||
- **Reporting**: PSD (Power Spectral Density) reports and pedestrian/head impact reports
|
||||
|
||||
The application follows a modular architecture using Prism Framework with Unity dependency injection, enabling loose coupling between components and supporting feature extensibility.
|
||||
|
||||
---
|
||||
|
||||
## Solution Structure
|
||||
|
||||
| Project | Path | Purpose |
|
||||
|---------|------|---------|
|
||||
| **DataPRO** | `DataPRO/DataPRO/` | Main WPF application (shell, bootstrapper, main window) |
|
||||
| **DASFactory** | `DataPRO/DASFactory/` | Hardware discovery and DAS device management factory |
|
||||
| **IService** | `DataPRO/IService/` | Core service interfaces and SLICE/TDAS service implementations |
|
||||
| **DbAPI** | `DataPRO/DbAPI/` | Database API layer for SQL Server operations |
|
||||
| **SensorDB** | `DataPRO/SensorDB/` | Sensor database models and calibration data |
|
||||
| **DASFactoryDb** | `DataPRO/DASFactoryDb/` | Database wrapper for DAS factory operations |
|
||||
| **ICommand** | `DataPRO/ICommand/` | Command pattern interfaces for hardware communication |
|
||||
| **SLICECommands** | `DataPRO/SLICECommands/` | SLICE device command implementations |
|
||||
| **TDASCommands** | `DataPRO/TDASCommands/` | TDAS device command implementations |
|
||||
| **Reports** | `DataPRO/Reports/` | Report generation functionality |
|
||||
| **DTS.Viewer** | `DTS Viewer/DTS.Viewer/` | Data visualization and analysis modules |
|
||||
| **DTS.Common** | `Common/DTS.Common/` | Core interfaces, events, base classes |
|
||||
| **DTS.Common.DataModel** | `Common/DTS.Common.DataModel/` | Data models (Group, Sensor, TestGraph) |
|
||||
| **DTS.Common.Storage** | `Common/DTS.Common.Storage/` | Storage and serialization utilities |
|
||||
| **DTS.Common.DAS.Concepts** | `Common/DTS.Common.DAS.Concepts/` | DAS domain concepts and interfaces |
|
||||
| **[Module Projects]** | `DataPRO/Modules/*/` | 30+ Prism modules for UI features |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### MVVM (Model-View-ViewModel)
|
||||
|
||||
All UI modules follow strict MVVM pattern:
|
||||
|
||||
```
|
||||
View (XAML) ←→ ViewModel (INotifyPropertyChanged) ←→ Model (Business Logic)
|
||||
```
|
||||
|
||||
Example from `SensorsListModule.cs:42-43`:
|
||||
```csharp
|
||||
_unityContainer.RegisterType<ISensorsListView, SensorsListView>();
|
||||
_unityContainer.RegisterType<ISensorsListViewModel, SensorsListViewModel>();
|
||||
```
|
||||
|
||||
### Prism Framework
|
||||
|
||||
The application uses Prism 8.x for:
|
||||
|
||||
- **Modularity**: `IModule` implementations for feature isolation
|
||||
- **Region Management**: Dynamic view composition in UI regions
|
||||
- **Event Aggregation**: Pub/Sub messaging via `IEventAggregator`
|
||||
- **Dependency Injection**: Unity container integration
|
||||
|
||||
Bootstrapper configuration (`Bootstrapper.cs:179-220`):
|
||||
```csharp
|
||||
protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
|
||||
{
|
||||
moduleCatalog.AddModule<StatusAndProgressBar.StatusAndProgressBarModule>();
|
||||
moduleCatalog.AddModule<DatabaseServices.DatabaseServicesModule>();
|
||||
moduleCatalog.AddModule<SensorsList.SensorsListModule>();
|
||||
// ... 30+ modules registered
|
||||
}
|
||||
```
|
||||
|
||||
### Unity Dependency Injection
|
||||
|
||||
Services and ViewModels are registered with Unity:
|
||||
|
||||
```csharp
|
||||
// Singleton registration
|
||||
_unityContainer.RegisterType<IMainViewModel, MainViewModel>(
|
||||
new ContainerControlledLifetimeManager());
|
||||
|
||||
// Transient registration
|
||||
_unityContainer.RegisterType<ISensorsListView, SensorsListView>();
|
||||
```
|
||||
|
||||
### Event Aggregation Pattern
|
||||
|
||||
Inter-module communication uses Prism's `PubSubEvent<T>`:
|
||||
|
||||
Defined in `Common/DTS.Common/Events/`:
|
||||
- `TestEvent` - Test lifecycle events
|
||||
- `SensorChangedEvent` - Sensor modifications
|
||||
- `HardwareSavedEvent` - Hardware configuration saves
|
||||
- `GroupChannelsChangedEvent` - Channel group updates
|
||||
- `DASConfigurationEvent` - DAS configuration changes
|
||||
|
||||
Example event from `TestEvent.cs:11`:
|
||||
```csharp
|
||||
public class TestEvent : PubSubEvent<TestEventArg> { }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Dependency Graph
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Main Application"
|
||||
DataPRO[DataPRO Shell]
|
||||
end
|
||||
|
||||
subgraph "Core Services"
|
||||
DASFactory[DASFactory]
|
||||
IService[IService]
|
||||
DbAPI[DbAPI]
|
||||
SensorDB[SensorDB]
|
||||
end
|
||||
|
||||
subgraph "Common Libraries"
|
||||
DTS_Common[DTS.Common]
|
||||
DTS_DataModel[DTS.Common.DataModel]
|
||||
DTS_Storage[DTS.Common.Storage]
|
||||
DTS_DASConcepts[DTS.Common.DAS.Concepts]
|
||||
end
|
||||
|
||||
subgraph "Hardware Commands"
|
||||
SLICECmds[SLICECommands]
|
||||
TDASCmds[TDASCommands]
|
||||
RibeyeCmds[RibeyeCommands]
|
||||
end
|
||||
|
||||
subgraph "UI Modules"
|
||||
SensorsList[SensorsList Module]
|
||||
HardwareList[HardwareList Module]
|
||||
GroupList[GroupList Module]
|
||||
TestSetupsList[TestSetupsList Module]
|
||||
RealtimeModule[RealtimeModule]
|
||||
DatabaseServices[DatabaseServices Module]
|
||||
DTSViewer[DTS.Viewer]
|
||||
end
|
||||
|
||||
DataPRO --> DASFactory
|
||||
DataPRO --> IService
|
||||
DataPRO --> DbAPI
|
||||
DataPRO --> SensorsList
|
||||
DataPRO --> HardwareList
|
||||
DataPRO --> GroupList
|
||||
DataPRO --> TestSetupsList
|
||||
DataPRO --> RealtimeModule
|
||||
DataPRO --> DatabaseServices
|
||||
DataPRO --> DTSViewer
|
||||
|
||||
DASFactory --> IService
|
||||
DASFactory --> SLICECmds
|
||||
DASFactory --> TDASCmds
|
||||
|
||||
IService --> DTS_DASConcepts
|
||||
IService --> DTS_Storage
|
||||
IService --> SLICECmds
|
||||
IService --> TDASCmds
|
||||
|
||||
DbAPI --> SensorDB
|
||||
|
||||
SensorsList --> DTS_Common
|
||||
SensorsList --> DTS_DataModel
|
||||
HardwareList --> DTS_Common
|
||||
GroupList --> DTS_DataModel
|
||||
TestSetupsList --> DTS_Common
|
||||
|
||||
DTS_Common --> DTS_DataModel
|
||||
DTS_DataModel --> DTS_Storage
|
||||
DTS_Storage --> DTS_DASConcepts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Flow
|
||||
|
||||
### 1. Hardware Discovery Flow
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ USB/ │────▶│ DASFactory │────▶│ IService │
|
||||
│ Ethernet │ │ Discovery │ │ (Slice) │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌─────────────┐
|
||||
│ IEventAggr. │────▶│ Modules │
|
||||
│ Publish │ │ (Hardware) │
|
||||
└──────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
The `DASFactory` (`DASFactory.cs:110`) handles:
|
||||
- USB device enumeration (WinUSB, CDC)
|
||||
- Ethernet multicast discovery
|
||||
- Serial port detection
|
||||
|
||||
### 2. Test Configuration Flow
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Sensors │────▶│ Groups │────▶│ TestSetup │
|
||||
│ Module │ │ Module │ │ Module │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ SensorDB │ │ Group.cs │ │ DbAPI │
|
||||
│ (Models) │ │ (DataModel) │ │ (Persist) │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
### 3. Real-time Data Flow
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ DAS HW │────▶│ IService │────▶│ Realtime │
|
||||
│ (SLICE) │ │ (Stream) │ │ Module │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
│
|
||||
┌───────────────────┼───────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ UDP Stream │ │ Charts │ │ Storage │
|
||||
│ (TMATs) │ │ (Display) │ │ (DASDB) │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
### 4. Database Access Pattern
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
|
||||
│ Modules │────▶│ DbAPI │────▶│ SQL Server │
|
||||
│ (Views) │ │ (Static API) │ │ (Database) │
|
||||
└─────────────┘ └──────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
`DbAPI.cs:237-373` exposes static properties for each domain:
|
||||
```csharp
|
||||
public static IConnections Connections { get; }
|
||||
public static IDataRecorders DAS { get; }
|
||||
public static ISensors Sensors { get; }
|
||||
public static IGroups Groups { get; }
|
||||
public static ITestSetups TestSetups { get; }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Extension Points
|
||||
|
||||
### Adding a New Module
|
||||
|
||||
1. Create project in `DataPRO/Modules/[Category]/[ModuleName]/`
|
||||
2. Create Module class implementing `IModule`:
|
||||
```csharp
|
||||
[Module(ModuleName = "MyModule")]
|
||||
public class MyModule : IModule
|
||||
{
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
containerRegistry.RegisterSingleton<IMyView, MyView>();
|
||||
containerRegistry.RegisterSingleton<IMyViewModel, MyViewModel>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Register in `Bootstrapper.cs:ConfigureModuleCatalog()`
|
||||
4. Define region in `eAssemblyRegion` enum
|
||||
5. Add module to solution folder structure
|
||||
|
||||
### Adding New Hardware Support
|
||||
|
||||
1. Create commands in `DataPRO/[Device]Commands/`
|
||||
2. Add device handling in `DASFactory.cs:[Device].cs` partial class
|
||||
3. Create service class in `IService/Classes/[Device]Service.cs`
|
||||
4. Implement `IDASCommunication` interface
|
||||
|
||||
### Adding New Events
|
||||
|
||||
1. Create event class in `Common/DTS.Common/Events/`:
|
||||
```csharp
|
||||
public class MyEvent : PubSubEvent<MyEventArgs> { }
|
||||
```
|
||||
|
||||
2. Create args class:
|
||||
```csharp
|
||||
public class MyEventArgs
|
||||
{
|
||||
public string Data { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
3. Publish from ViewModel:
|
||||
```csharp
|
||||
_eventAggregator.GetEvent<MyEvent>().Publish(new MyEventArgs { Data = "value" });
|
||||
```
|
||||
|
||||
4. Subscribe in target module:
|
||||
```csharp
|
||||
_eventAggregator.GetEvent<MyEvent>().Subscribe(OnMyEvent);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Technologies
|
||||
|
||||
### Frameworks & Libraries
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|------------|---------|---------|
|
||||
| .NET Framework | 4.8 | Runtime |
|
||||
| Prism | 8.x | MVVM framework, modularity |
|
||||
| Unity | 2.x | Dependency injection container |
|
||||
| Stateless | 4.2.1 | State machine for DAS operations |
|
||||
| Newtonsoft.Json | Latest | JSON serialization |
|
||||
| ComponentOne WPF | 4.0.20203 | Charts and UI controls |
|
||||
| Xceed WPF Toolkit | 3.0 | Extended WPF controls |
|
||||
|
||||
### Communication Protocols
|
||||
|
||||
- **USB**: WinUSB, CDC for SLICE devices
|
||||
- **Ethernet**: TCP/UDP, multicast discovery
|
||||
- **Serial**: RS-232 for legacy TDAS
|
||||
- **CAN**: CAN-FD for SLICE Pro
|
||||
|
||||
### Database
|
||||
|
||||
- **SQL Server**: Primary data storage
|
||||
- **Stored Procedures**: Versioned SPs for DB compatibility
|
||||
- **DASFactoryDb**: Local SQLite for device caching
|
||||
|
||||
---
|
||||
|
||||
## Gotchas and Tech Debt
|
||||
|
||||
### Architecture Issues
|
||||
|
||||
1. **Static DbAPI Access** (`DbAPI.cs:237-373`)
|
||||
- Static properties make testing difficult
|
||||
- No interface abstraction for database layer
|
||||
- Recommendation: Convert to injected `IDbAPI` service
|
||||
|
||||
2. **App.xaml.cs God Class** (4620 lines)
|
||||
- Contains licensing, test state, UI logic, database operations
|
||||
- Violates Single Responsibility Principle
|
||||
- Recommendation: Extract into separate services
|
||||
|
||||
3. **Mixed Module Initialization**
|
||||
- Some modules use `Initialize()`, others `RegisterTypes()`
|
||||
- Example: `SensorsListModule.cs:39` vs `DatabaseServicesModule.cs:38`
|
||||
|
||||
### Threading Concerns
|
||||
|
||||
1. **UI Thread Dependencies** (`App.xaml.cs:128-161`)
|
||||
- `StartTest()` method called from UI, affects global state
|
||||
- `RunTestVariables` static class for test configuration
|
||||
|
||||
2. **Device Queue Processing** (`DASFactory.cs:127`)
|
||||
```csharp
|
||||
private BlockingCollection<Tuple<QueueActions, DeviceHandling>> _queueActionPerDevice;
|
||||
```
|
||||
|
||||
### Database Versioning
|
||||
|
||||
- Stored procedures are versioned (`DbAPI.cs:34-97`)
|
||||
- Client and server DB versions must be negotiated
|
||||
- SP version caching implemented but fragile
|
||||
|
||||
### State Machine Complexity
|
||||
|
||||
IService uses Stateless for DAS operations (`IService/StateMachine/`):
|
||||
- States: Prepare, Configure, Arm, Realtime, Download, Diagnose
|
||||
- Complex state transitions can be difficult to debug
|
||||
|
||||
### Legacy Code Patterns
|
||||
|
||||
1. **WinForms Interop** in some modules
|
||||
2. **BackgroundWorker** instead of async/await in places
|
||||
3. **Mixed logging** - custom `APILogger` and `TextLogger` classes
|
||||
|
||||
### Missing Unit Tests
|
||||
|
||||
Limited test coverage in:
|
||||
- `DataPRO/UnitTest/` - only database tests present
|
||||
- No ViewModel tests
|
||||
- No service layer tests
|
||||
|
||||
---
|
||||
|
||||
## Key File References
|
||||
|
||||
| Component | File | Lines |
|
||||
|-----------|------|-------|
|
||||
| Bootstrapper | `DataPRO/DataPRO/Bootstrapper.cs` | 223 |
|
||||
| Application Entry | `DataPRO/DataPRO/App.xaml.cs` | 4620 |
|
||||
| DAS Factory | `DataPRO/DASFactory/DASFactory.cs` | 1712 |
|
||||
| SLICE Service | `DataPRO/IService/Classes/SLICEService/SLICE Service.cs` | 480+ |
|
||||
| Database API | `DataPRO/DbAPI/DbAPI.cs` | 375 |
|
||||
| Sensor Database | `DataPRO/SensorDB/SensorDB.cs` | 983 |
|
||||
| Group Model | `Common/DTS.Common.DataModel/Group.cs` | 2452 |
|
||||
| Events | `Common/DTS.Common/Events/*.cs` | 129 events |
|
||||
|
||||
---
|
||||
|
||||
## Module Summary
|
||||
|
||||
| Category | Modules |
|
||||
|----------|---------|
|
||||
| **Prepare** | SensorsList, HardwareList, GroupList, GroupChannelList, TestSetupsList, CachedItemsList, ChannelCodes |
|
||||
| **Configure** | SensorSettingsModule, SoftwareFilters, AddEditHardware, GroupImport, Diagnostics |
|
||||
| **System** | DatabaseServices, DBImportExport, RealtimeSettings, TestSettings, ISOSettings, QASettings, UISettings, TablesSettings, PowerAndBattery |
|
||||
| **Realtime** | RealtimeModule, StatusAndProgressBar |
|
||||
| **Viewer** | DTS.Viewer, Graph, GraphList, Filter, ChartOptions, TestModification, TestSummaryList, Navigation, AddCalculatedChannel, ViewerSettings |
|
||||
| **Reports** | PSDReport, PSDReportSettings, PSDReportResults, PedestrianAndHeadReports |
|
||||
| **ISO** | ExtraProperties, RegionOfInterestChannels |
|
||||
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
|
||||
492
GLM5Analysis/PromptTemplates/AddNewHardwareSupport.md
Normal file
492
GLM5Analysis/PromptTemplates/AddNewHardwareSupport.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# Add New Hardware Support - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO supports various Data Acquisition System (DAS) hardware through the hardware abstraction layer in `Common/DTS.Common.DAS.Concepts/` and the `DataPRO/Modules/Hardware/` modules. Hardware support involves implementing interfaces for arm/disarm, data collection, real-time streaming, and trigger functionality.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
Common/DTS.Common.DAS.Concepts/
|
||||
├── DAS/
|
||||
│ ├── Channel/ # Channel abstraction
|
||||
│ ├── DAS.Id.cs # Hardware identification
|
||||
│ └── DAS.Channel.cs # Channel definitions
|
||||
├── Interfaces/ # Hardware interfaces
|
||||
├── IArmable.cs # Arming capability
|
||||
├── IDataCollectionEnabled.cs # Data collection
|
||||
├── IDownloadEnabled.cs # Data download
|
||||
├── IRealtimeable.cs # Real-time streaming
|
||||
├── ITriggerable.cs # Trigger capability
|
||||
└── IGpioEnabled.cs # GPIO support
|
||||
|
||||
DataPRO/Modules/Hardware/
|
||||
├── HardwareList/ # Hardware management UI
|
||||
│ ├── Model/
|
||||
│ ├── View/
|
||||
│ └── ViewModel/
|
||||
└── AddEditHardware/ # Hardware configuration
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create Hardware Model
|
||||
**File:** `Common/DTS.Common.DAS.Concepts/DAS/{HardwareName}.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DTS.Common.DAS.Concepts
|
||||
{
|
||||
public class {HARDWARE_NAME} :
|
||||
IArmable,
|
||||
IDataCollectionEnabled,
|
||||
IRealtimeable,
|
||||
ITriggerable
|
||||
{
|
||||
public DASId Id { get; set; }
|
||||
public string SerialNumber { get; set; }
|
||||
public string FirmwareVersion { get; set; }
|
||||
public int ChannelCount { get; set; }
|
||||
public int SampleRate { get; set; }
|
||||
|
||||
public ArmStatus ArmStatus { get; private set; }
|
||||
public bool IsDataCollectionEnabled { get; private set; }
|
||||
public bool IsRealtimeEnabled { get; private set; }
|
||||
|
||||
public {HARDWARE_NAME}()
|
||||
{
|
||||
Id = new DASId();
|
||||
ChannelCount = 8;
|
||||
SampleRate = 100000;
|
||||
ArmStatus = ArmStatus.Disarmed;
|
||||
}
|
||||
|
||||
// IArmable implementation
|
||||
public void Arm()
|
||||
{
|
||||
ValidateArmConditions();
|
||||
SendArmCommand();
|
||||
ArmStatus = ArmStatus.Armed;
|
||||
}
|
||||
|
||||
public void Disarm()
|
||||
{
|
||||
SendDisarmCommand();
|
||||
ArmStatus = ArmStatus.Disarmed;
|
||||
}
|
||||
|
||||
public AvailableArmModes GetAvailableArmModes()
|
||||
{
|
||||
return new AvailableArmModes
|
||||
{
|
||||
Modes = new List<ArmMode>
|
||||
{
|
||||
ArmMode.Triggered,
|
||||
ArmMode.Immediate
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// IDataCollectionEnabled implementation
|
||||
public void EnableDataCollection()
|
||||
{
|
||||
ConfigureDataCollection();
|
||||
IsDataCollectionEnabled = true;
|
||||
}
|
||||
|
||||
public void DisableDataCollection()
|
||||
{
|
||||
StopDataCollection();
|
||||
IsDataCollectionEnabled = false;
|
||||
}
|
||||
|
||||
// IRealtimeable implementation
|
||||
public void StartRealtime()
|
||||
{
|
||||
ValidateRealtimeConditions();
|
||||
StartRealtimeStreaming();
|
||||
IsRealtimeEnabled = true;
|
||||
}
|
||||
|
||||
public void StopRealtime()
|
||||
{
|
||||
StopRealtimeStreaming();
|
||||
IsRealtimeEnabled = false;
|
||||
}
|
||||
|
||||
// ITriggerable implementation
|
||||
public void ConfigureTrigger(TriggerConfiguration config)
|
||||
{
|
||||
ValidateTriggerConfiguration(config);
|
||||
ApplyTriggerSettings(config);
|
||||
}
|
||||
|
||||
// Hardware-specific methods
|
||||
private void ValidateArmConditions()
|
||||
{
|
||||
if (string.IsNullOrEmpty(SerialNumber))
|
||||
throw new InvalidOperationException("Serial number required");
|
||||
}
|
||||
|
||||
private void SendArmCommand()
|
||||
{
|
||||
// Hardware communication implementation
|
||||
}
|
||||
|
||||
private void SendDisarmCommand()
|
||||
{
|
||||
// Hardware communication implementation
|
||||
}
|
||||
|
||||
private void ConfigureDataCollection()
|
||||
{
|
||||
// Configure sample rate, duration, etc.
|
||||
}
|
||||
|
||||
private void StopDataCollection()
|
||||
{
|
||||
// Stop collection
|
||||
}
|
||||
|
||||
private void ValidateRealtimeConditions()
|
||||
{
|
||||
if (!IsDataCollectionEnabled)
|
||||
throw new InvalidOperationException("Enable data collection first");
|
||||
}
|
||||
|
||||
private void StartRealtimeStreaming()
|
||||
{
|
||||
// Start real-time data stream
|
||||
}
|
||||
|
||||
private void StopRealtimeStreaming()
|
||||
{
|
||||
// Stop real-time data stream
|
||||
}
|
||||
|
||||
private void ValidateTriggerConfiguration(TriggerConfiguration config)
|
||||
{
|
||||
if (config.Threshold < 0)
|
||||
throw new ArgumentException("Invalid threshold");
|
||||
}
|
||||
|
||||
private void ApplyTriggerSettings(TriggerConfiguration config)
|
||||
{
|
||||
// Apply trigger configuration to hardware
|
||||
}
|
||||
}
|
||||
|
||||
public class TriggerConfiguration
|
||||
{
|
||||
public double Threshold { get; set; }
|
||||
public TriggerEdge Edge { get; set; }
|
||||
public int Channel { get; set; }
|
||||
}
|
||||
|
||||
public enum TriggerEdge
|
||||
{
|
||||
Rising,
|
||||
Falling,
|
||||
Both
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Hardware Channel Definition
|
||||
**File:** `Common/DTS.Common.DAS.Concepts/DAS/{HardwareName}Channel.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using DTS.Common.DAS.Concepts.Channel;
|
||||
|
||||
namespace DTS.Common.DAS.Concepts
|
||||
{
|
||||
public class {HARDWARE_NAME}Channel : DAS.Channel
|
||||
{
|
||||
public int PhysicalChannel { get; set; }
|
||||
public string Label { get; set; }
|
||||
public double FullScaleRange { get; set; }
|
||||
public double ExcitationVoltage { get; set; }
|
||||
|
||||
public {HARDWARE_NAME}Channel(int index)
|
||||
{
|
||||
PhysicalChannel = index;
|
||||
FullScaleRange = 10.0;
|
||||
ExcitationVoltage = 0.0;
|
||||
}
|
||||
|
||||
public void Configure(ChannelConfiguration config)
|
||||
{
|
||||
FullScaleRange = config.FullScaleRange;
|
||||
ExcitationVoltage = config.ExcitationVoltage;
|
||||
ApplyChannelSettings();
|
||||
}
|
||||
|
||||
private void ApplyChannelSettings()
|
||||
{
|
||||
// Apply channel configuration to hardware
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelConfiguration
|
||||
{
|
||||
public double FullScaleRange { get; set; }
|
||||
public double ExcitationVoltage { get; set; }
|
||||
public CouplingMode Coupling { get; set; }
|
||||
}
|
||||
|
||||
public enum CouplingMode
|
||||
{
|
||||
DC,
|
||||
AC
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create Hardware Factory
|
||||
**File:** `Common/DTS.Common.DAS.Concepts/DAS/{HardwareName}Factory.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using DTS.Common.DAS.Concepts.Interfaces;
|
||||
|
||||
namespace DTS.Common.DAS.Concepts
|
||||
{
|
||||
public class {HARDWARE_NAME}Factory : IDASFactory
|
||||
{
|
||||
public string HardwareType => "{HARDWARE_NAME}";
|
||||
|
||||
public DAS.Channel CreateChannel(int index)
|
||||
{
|
||||
return new {HARDWARE_NAME}Channel(index);
|
||||
}
|
||||
|
||||
public {HARDWARE_NAME} CreateHardware(string serialNumber)
|
||||
{
|
||||
var hardware = new {HARDWARE_NAME}
|
||||
{
|
||||
SerialNumber = serialNumber
|
||||
};
|
||||
|
||||
InitializeHardware(hardware);
|
||||
return hardware;
|
||||
}
|
||||
|
||||
private void InitializeHardware({HARDWARE_NAME} hardware)
|
||||
{
|
||||
// Hardware initialization
|
||||
// Connect, discover channels, read firmware version
|
||||
}
|
||||
|
||||
public bool CanCreate(string hardwareIdentifier)
|
||||
{
|
||||
// Check if this factory supports the given hardware
|
||||
return hardwareIdentifier.StartsWith("{HARDWARE_PREFIX}");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Update Hardware List Module
|
||||
**File:** `DataPRO/Modules/Hardware/HardwareList/Model/{HardwareName}Model.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using DTS.Common.DAS.Concepts;
|
||||
|
||||
namespace HardwareList.Model
|
||||
{
|
||||
public class {HARDWARE_NAME}Model : INotifyPropertyChanged
|
||||
{
|
||||
private {HARDWARE_NAME} _hardware;
|
||||
|
||||
public string SerialNumber
|
||||
{
|
||||
get => _hardware?.SerialNumber;
|
||||
set
|
||||
{
|
||||
if (_hardware != null)
|
||||
_hardware.SerialNumber = value;
|
||||
OnPropertyChanged(nameof(SerialNumber));
|
||||
}
|
||||
}
|
||||
|
||||
public string FirmwareVersion => _hardware?.FirmwareVersion;
|
||||
public int ChannelCount => _hardware?.ChannelCount ?? 0;
|
||||
public ArmStatus ArmStatus => _hardware?.ArmStatus ?? ArmStatus.Disarmed;
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public void SetHardware({HARDWARE_NAME} hardware)
|
||||
{
|
||||
_hardware = hardware;
|
||||
OnPropertyChanged(nameof(SerialNumber));
|
||||
OnPropertyChanged(nameof(FirmwareVersion));
|
||||
OnPropertyChanged(nameof(ChannelCount));
|
||||
OnPropertyChanged(nameof(ArmStatus));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Create Hardware View
|
||||
**File:** `DataPRO/Modules/Hardware/HardwareList/View/{HardwareName}View.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="HardwareList.View.{HARDWARE_NAME}View"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:HardwareList.Resources">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{x:Static resx:StringResources.{HARDWARE_NAME}_Header}"
|
||||
Style="{StaticResource HardwareHeaderStyle}"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
||||
<TextBlock Text="{x:Static resx:StringResources.SerialNumber}"/>
|
||||
<TextBox Text="{Binding SerialNumber, Mode=TwoWay}" Width="150" Margin="5,0"/>
|
||||
<TextBlock Text="{x:Static resx:StringResources.Firmware}"/>
|
||||
<TextBlock Text="{Binding FirmwareVersion}" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<TabControl Grid.Row="2">
|
||||
<TabItem Header="{x:Static resx:StringResources.Channels}">
|
||||
<ListView ItemsSource="{Binding Channels}" SelectedItem="{Binding SelectedChannel}">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="Channel" DisplayMemberBinding="{Binding PhysicalChannel}"/>
|
||||
<GridViewColumn Header="Label" DisplayMemberBinding="{Binding Label}"/>
|
||||
<GridViewColumn Header="Range" DisplayMemberBinding="{Binding FullScaleRange}"/>
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</TabItem>
|
||||
<TabItem Header="{x:Static resx:StringResources.Settings}">
|
||||
<!-- Hardware-specific settings -->
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
### 6. Register Factory in Bootstrapper
|
||||
**File:** Modify the bootstrapper or module initialization
|
||||
|
||||
```csharp
|
||||
private void RegisterDASFactories()
|
||||
{
|
||||
// Register existing factories
|
||||
_unityContainer.RegisterType<IDASFactory, SLICE6Factory>("SLICE6");
|
||||
_unityContainer.RegisterType<IDASFactory, TDASFactory>("TDAS");
|
||||
|
||||
// Register new hardware factory
|
||||
_unityContainer.RegisterType<IDASFactory, {HARDWARE_NAME}Factory>("{HARDWARE_NAME}");
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Add Hardware Type to Enums
|
||||
**File:** `Common/DTS.Common/Enums/HardwareTypes.cs`
|
||||
|
||||
```csharp
|
||||
public enum HardwareType
|
||||
{
|
||||
Unknown,
|
||||
SLICE6,
|
||||
TDAS,
|
||||
{HARDWARE_NAME} // Add new type
|
||||
}
|
||||
```
|
||||
|
||||
## Interface Reference
|
||||
|
||||
### IArmable
|
||||
```csharp
|
||||
public interface IArmable
|
||||
{
|
||||
ArmStatus ArmStatus { get; }
|
||||
void Arm();
|
||||
void Disarm();
|
||||
AvailableArmModes GetAvailableArmModes();
|
||||
}
|
||||
```
|
||||
|
||||
### IDataCollectionEnabled
|
||||
```csharp
|
||||
public interface IDataCollectionEnabled
|
||||
{
|
||||
bool IsDataCollectionEnabled { get; }
|
||||
void EnableDataCollection();
|
||||
void DisableDataCollection();
|
||||
}
|
||||
```
|
||||
|
||||
### IRealtimeable
|
||||
```csharp
|
||||
public interface IRealtimeable
|
||||
{
|
||||
bool IsRealtimeEnabled { get; }
|
||||
void StartRealtime();
|
||||
void StopRealtime();
|
||||
}
|
||||
```
|
||||
|
||||
### ITriggerable
|
||||
```csharp
|
||||
public interface ITriggerable
|
||||
{
|
||||
void ConfigureTrigger(TriggerConfiguration config);
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create/Modify Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `DTS.Common.DAS.Concepts/DAS/{HardwareName}.cs` | Create |
|
||||
| `DTS.Common.DAS.Concepts/DAS/{HardwareName}Channel.cs` | Create |
|
||||
| `DTS.Common.DAS.Concepts/DAS/{HardwareName}Factory.cs` | Create |
|
||||
| `HardwareList/Model/{HardwareName}Model.cs` | Create |
|
||||
| `HardwareList/View/{HardwareName}View.xaml` | Create |
|
||||
| `HardwareList/View/{HardwareName}View.xaml.cs` | Create |
|
||||
| `HardwareList/HardwareListModule.cs` | Modify (register) |
|
||||
| `DTS.Common/Enums/HardwareType.cs` | Modify (add enum) |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Hardware class implements required interfaces (`IArmable`, etc.)
|
||||
- [ ] Channel class inherits from `DAS.Channel`
|
||||
- [ ] Factory implements `IDASFactory`
|
||||
- [ ] All interface methods properly implemented
|
||||
- [ ] Validation added for hardware commands
|
||||
- [ ] Error handling for communication failures
|
||||
- [ ] Factory registered in DI container
|
||||
- [ ] Hardware type added to enum
|
||||
- [ ] View properly data-bound to ViewModel
|
||||
- [ ] Localization strings added
|
||||
- [ ] Channel configuration supports hardware features
|
||||
|
||||
## Hardware Communication Patterns
|
||||
|
||||
1. **Synchronous Commands:** Use for arm/disarm operations
|
||||
2. **Asynchronous Data:** Use for real-time streaming
|
||||
3. **Status Polling:** Implement periodic status checks
|
||||
4. **Error Recovery:** Handle disconnections gracefully
|
||||
5. **Timeout Handling:** Set appropriate timeouts for operations
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
- Always check connection status before sending commands
|
||||
- Validate hardware state transitions (can't arm if already armed)
|
||||
- Handle firmware version differences
|
||||
- Consider backward compatibility with older firmware
|
||||
- Implement proper disposal of resources
|
||||
464
GLM5Analysis/PromptTemplates/AddNewImportFormat.md
Normal file
464
GLM5Analysis/PromptTemplates/AddNewImportFormat.md
Normal file
@@ -0,0 +1,464 @@
|
||||
# Add New Import Format - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO supports importing test data from various file formats through the `Common/DTS.Common.Import/` library. The import system uses a parser-based architecture where each format has a dedicated parser class that converts external data into the internal `ImportObject` structure.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
Common/DTS.Common.Import/
|
||||
├── Parsers/
|
||||
│ ├── CSV/ # CSV format parsers
|
||||
│ ├── EQX/ # Equipment Exchange format
|
||||
│ │ ├── EQXSensorsParser.cs
|
||||
│ │ ├── EQXTestSetupParser.cs
|
||||
│ │ └── EQXGroupImport.cs
|
||||
│ ├── DefaultParseImport.cs
|
||||
│ ├── DTSXMLParseImport.cs
|
||||
│ └── ParseVariantBase.cs # Base class for parsers
|
||||
├── ImportObject.cs # Container for imported data
|
||||
├── ImportError.cs # Error handling
|
||||
├── ImportOptions/ # Format-specific options
|
||||
├── Interfaces/ # Parser interfaces
|
||||
├── Persist/ # Database persistence
|
||||
└── XML/ # XML processing utilities
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create the Parser Class
|
||||
**File:** `Common/DTS.Common.Import/Parsers/{FormatName}/{FormatName}Parser.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using DTS.Common.Enums;
|
||||
using DTS.Common.Import.Enums;
|
||||
using DTS.Common.Import.ImportOptions;
|
||||
using DTS.Common.Import.Parsers;
|
||||
using DTS.Common.Interface.Sensors;
|
||||
using DTS.Common.Storage;
|
||||
using DTS.SensorDB;
|
||||
|
||||
namespace DTS.Common.Import
|
||||
{
|
||||
public class {FORMAT_NAME}Parser : ParseVariantBase
|
||||
{
|
||||
private readonly User _currentUser;
|
||||
private readonly IImportNotification _importNotification;
|
||||
private readonly {FORMAT_NAME}ImportOptions _importOptions;
|
||||
|
||||
public {FORMAT_NAME}Parser(
|
||||
IImportNotification importNotification,
|
||||
User user,
|
||||
{FORMAT_NAME}ImportOptions importOptions)
|
||||
{
|
||||
_currentUser = user;
|
||||
_importNotification = importNotification;
|
||||
_importOptions = importOptions;
|
||||
}
|
||||
|
||||
public override void Parse(ref ImportObject importObject)
|
||||
{
|
||||
if (string.IsNullOrEmpty(FileName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (importObject == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(importObject),
|
||||
"importObject can't be null");
|
||||
}
|
||||
|
||||
importObject = ParseFile(importObject, FileName);
|
||||
}
|
||||
|
||||
private ImportObject ParseFile(ImportObject importObject, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate file exists
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
importObject.AddError(new ImportError(
|
||||
$"File not found: {filePath}"));
|
||||
return importObject;
|
||||
}
|
||||
|
||||
// Read and parse file content
|
||||
var content = File.ReadAllText(filePath);
|
||||
var parsedData = ParseContent(content);
|
||||
|
||||
// Populate import object
|
||||
PopulateImportObject(importObject, parsedData);
|
||||
|
||||
// Set source format
|
||||
importObject.SourceFormat = ImportFormats.{FORMAT_NAME};
|
||||
|
||||
_importNotification?.NotifyProgress(100, "Import complete");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
importObject.AddError(new ImportError(
|
||||
$"Parse error: {ex.Message}"));
|
||||
}
|
||||
|
||||
return importObject;
|
||||
}
|
||||
|
||||
private ParsedData ParseContent(string content)
|
||||
{
|
||||
var data = new ParsedData();
|
||||
|
||||
// Format-specific parsing logic here
|
||||
// Example: Parse lines, extract sensors, test setups, etc.
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private void PopulateImportObject(ImportObject importObject, ParsedData data)
|
||||
{
|
||||
// Add sensors
|
||||
foreach (var sensor in data.Sensors)
|
||||
{
|
||||
importObject.AddSensor(ConvertToSensorData(sensor));
|
||||
}
|
||||
|
||||
// Add test setups
|
||||
foreach (var setup in data.TestSetups)
|
||||
{
|
||||
importObject.AddTestSetup(ConvertToTestTemplate(setup));
|
||||
}
|
||||
|
||||
// Add hardware
|
||||
foreach (var hardware in data.Hardware)
|
||||
{
|
||||
importObject.AddHardware(ConvertToDASHardware(hardware));
|
||||
}
|
||||
}
|
||||
|
||||
private SensorData ConvertToSensorData(ParsedSensor parsed)
|
||||
{
|
||||
var sensorData = new SensorData
|
||||
{
|
||||
Name = parsed.Name,
|
||||
SerialNumber = parsed.SerialNumber,
|
||||
ChannelCode = parsed.ChannelCode,
|
||||
CalibrationFactor = parsed.CalibrationFactor,
|
||||
EngineeringUnits = parsed.EngineeringUnits,
|
||||
Sensitivity = parsed.Sensitivity
|
||||
};
|
||||
|
||||
return sensorData;
|
||||
}
|
||||
|
||||
private TestTemplate ConvertToTestTemplate(ParsedTestSetup parsed)
|
||||
{
|
||||
// Convert parsed test setup to TestTemplate
|
||||
return new TestTemplate();
|
||||
}
|
||||
|
||||
private DASHardware ConvertToDASHardware(ParsedHardware parsed)
|
||||
{
|
||||
// Convert parsed hardware to DASHardware
|
||||
return new DASHardware();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ParsedData
|
||||
{
|
||||
public List<ParsedSensor> Sensors { get; set; } = new List<ParsedSensor>();
|
||||
public List<ParsedTestSetup> TestSetups { get; set; } = new List<ParsedTestSetup>();
|
||||
public List<ParsedHardware> Hardware { get; set; } = new List<ParsedHardware>();
|
||||
}
|
||||
|
||||
internal class ParsedSensor
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string SerialNumber { get; set; }
|
||||
public string ChannelCode { get; set; }
|
||||
public double CalibrationFactor { get; set; }
|
||||
public string EngineeringUnits { get; set; }
|
||||
public double Sensitivity { get; set; }
|
||||
}
|
||||
|
||||
internal class ParsedTestSetup { }
|
||||
internal class ParsedHardware { }
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Import Options Class
|
||||
**File:** `Common/DTS.Common.Import/ImportOptions/{FormatName}ImportOptions.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
|
||||
namespace DTS.Common.Import.ImportOptions
|
||||
{
|
||||
public class {FORMAT_NAME}ImportOptions
|
||||
{
|
||||
public bool ImportSensors { get; set; } = true;
|
||||
public bool ImportTestSetups { get; set; } = true;
|
||||
public bool ImportHardware { get; set; } = false;
|
||||
public bool CreateGroups { get; set; } = true;
|
||||
public bool ValidateData { get; set; } = true;
|
||||
|
||||
// Format-specific options
|
||||
public string DateTimeFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
|
||||
public string Delimiter { get; set; } = ",";
|
||||
public bool HasHeaderRow { get; set; } = true;
|
||||
public int SkipRows { get; set; } = 0;
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Delimiter))
|
||||
throw new ArgumentException("Delimiter is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Update Import Formats Enum
|
||||
**File:** `Common/DTS.Common.Import/Enums/ImportFormats.cs`
|
||||
|
||||
```csharp
|
||||
namespace DTS.Common.Import.Enums
|
||||
{
|
||||
public enum ImportFormats
|
||||
{
|
||||
NOT_SPECIFIED,
|
||||
CSV,
|
||||
EQX,
|
||||
DTS_XML,
|
||||
{FORMAT_NAME} // Add new format
|
||||
}
|
||||
|
||||
public enum ImportFileFormat
|
||||
{
|
||||
NoTestSetup,
|
||||
SingleTestSetup,
|
||||
MultipleTestSetup
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Parser Factory Registration
|
||||
**File:** `Common/DTS.Common.Import/Factories/{FormatName}ParserFactory.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using DTS.Common.Import.ImportOptions;
|
||||
using DTS.Common.Import.Parsers;
|
||||
using DTS.Common.Interface.Sensors;
|
||||
using DTS.SensorDB;
|
||||
|
||||
namespace DTS.Common.Import.Factories
|
||||
{
|
||||
public class {FORMAT_NAME}ParserFactory
|
||||
{
|
||||
public static {FORMAT_NAME}Parser Create(
|
||||
IImportNotification importNotification,
|
||||
User user,
|
||||
{FORMAT_NAME}ImportOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
options = new {FORMAT_NAME}ImportOptions();
|
||||
}
|
||||
|
||||
return new {FORMAT_NAME}Parser(importNotification, user, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Add File Detection Logic
|
||||
**File:** `Common/DTS.Common.Import/ImportObject.cs` (modify)
|
||||
|
||||
Add method to detect format from file:
|
||||
```csharp
|
||||
public static ImportFormats DetectFormat(string filePath)
|
||||
{
|
||||
var extension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
case ".csv":
|
||||
return ImportFormats.CSV;
|
||||
case ".eqx":
|
||||
return ImportFormats.EQX;
|
||||
case ".{FORMAT_EXTENSION}":
|
||||
return ImportFormats.{FORMAT_NAME};
|
||||
default:
|
||||
// Check file content for format signature
|
||||
return DetectFormatFromContent(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private static ImportFormats DetectFormatFromContent(string filePath)
|
||||
{
|
||||
// Read first few lines to detect format
|
||||
var firstLine = File.ReadLines(filePath).FirstOrDefault();
|
||||
|
||||
if (firstLine != null)
|
||||
{
|
||||
// Check for format-specific signatures
|
||||
if (firstLine.StartsWith("{FORMAT_SIGNATURE}"))
|
||||
{
|
||||
return ImportFormats.{FORMAT_NAME};
|
||||
}
|
||||
}
|
||||
|
||||
return ImportFormats.NOT_SPECIFIED;
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Create Unit Tests
|
||||
**File:** `Common/DTS.Common.Tests/{FormatName}ParserShould.cs`
|
||||
|
||||
```csharp
|
||||
using DTS.Common.Import;
|
||||
using DTS.Common.Import.ImportOptions;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace DTS.Common.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class {FORMAT_NAME}ParserShould
|
||||
{
|
||||
private {FORMAT_NAME}Parser _parser;
|
||||
private {FORMAT_NAME}ImportOptions _options;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_options = new {FORMAT_NAME}ImportOptions();
|
||||
_parser = new {FORMAT_NAME}Parser(null, null, _options);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_ShouldReturnEmptyImportObject_WhenFileNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
var importObject = new ImportObject();
|
||||
_parser.FileName = null;
|
||||
|
||||
// Act
|
||||
_parser.Parse(ref importObject);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(importObject);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_ShouldThrowArgumentNullException_WhenImportObjectIsNull()
|
||||
{
|
||||
// Arrange
|
||||
ImportObject importObject = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => _parser.Parse(ref importObject));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Parse_ShouldImportSensors_WhenValidFile()
|
||||
{
|
||||
// Arrange
|
||||
var importObject = new ImportObject();
|
||||
var testFile = CreateTestFile();
|
||||
_parser.FileName = testFile;
|
||||
|
||||
// Act
|
||||
_parser.Parse(ref importObject);
|
||||
|
||||
// Assert
|
||||
// Add assertions for expected data
|
||||
|
||||
// Cleanup
|
||||
File.Delete(testFile);
|
||||
}
|
||||
|
||||
private string CreateTestFile()
|
||||
{
|
||||
var path = Path.GetTempFileName();
|
||||
// Write test content
|
||||
File.WriteAllText(path, "test content");
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create/Modify Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `Parsers/{FormatName}/{FormatName}Parser.cs` | Create |
|
||||
| `ImportOptions/{FormatName}ImportOptions.cs` | Create |
|
||||
| `Factories/{FormatName}ParserFactory.cs` | Create |
|
||||
| `Enums/ImportFormats.cs` | Modify (add enum value) |
|
||||
| `ImportObject.cs` | Modify (add detection logic) |
|
||||
| `DTS.Common.Tests/{FormatName}ParserShould.cs` | Create |
|
||||
|
||||
## Parser Base Class Reference
|
||||
|
||||
```csharp
|
||||
public abstract class ParseVariantBase
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public abstract void Parse(ref ImportObject importObject);
|
||||
}
|
||||
```
|
||||
|
||||
## ImportObject Key Methods
|
||||
|
||||
```csharp
|
||||
// Adding data to import object
|
||||
void AddSensor(SensorData sensor);
|
||||
void AddTestSetup(TestTemplate template);
|
||||
void AddHardware(DASHardware hardware);
|
||||
void AddCalibration(SensorCalibration calibration);
|
||||
|
||||
// Error handling
|
||||
void AddError(ImportError error);
|
||||
IEnumerable<ImportError> Errors();
|
||||
|
||||
// Format detection
|
||||
ImportFormats GetImportFileFormat();
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Parser inherits from `ParseVariantBase`
|
||||
- [ ] `Parse()` method validates null inputs
|
||||
- [ ] File existence checked before parsing
|
||||
- [ ] Errors added to `ImportObject` for failures
|
||||
- [ ] Progress notification via `IImportNotification`
|
||||
- [ ] Source format set on `ImportObject`
|
||||
- [ ] Import options class with validation
|
||||
- [ ] Format added to `ImportFormats` enum
|
||||
- [ ] File detection logic implemented
|
||||
- [ ] Unit tests for happy path and error cases
|
||||
- [ ] Test file cleanup in tests
|
||||
|
||||
## Common Patterns
|
||||
|
||||
1. **Dependency Injection:** Parser receives notification and user objects
|
||||
2. **Error Accumulation:** Add errors to ImportObject rather than throwing
|
||||
3. **Progress Notification:** Call `NotifyProgress()` during long operations
|
||||
4. **File Validation:** Check existence before reading
|
||||
5. **Format Detection:** Check extension and content signature
|
||||
|
||||
## Supported Data Types
|
||||
|
||||
The `ImportObject` can hold:
|
||||
- `SensorData` - Sensor configurations
|
||||
- `TestTemplate` - Test setup definitions
|
||||
- `DASHardware` - Data acquisition hardware
|
||||
- `SensorCalibration` - Calibration data
|
||||
- `Group` - Test object groups
|
||||
- `ISO.CustomerDetails` - Customer information
|
||||
451
GLM5Analysis/PromptTemplates/AddNewReport.md
Normal file
451
GLM5Analysis/PromptTemplates/AddNewReport.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# Add New Report - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO reports are implemented as Prism modules in two locations:
|
||||
- `DataPRO/Modules/Reports/` - Full DataPRO reports
|
||||
- `DTS Viewer/DTS.Viewer.Reports/` - Viewer-specific reports
|
||||
|
||||
Reports follow MVVM with separate Input and Output views for parameter collection and results display.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
DataPRO/Modules/Reports/PedestrianAndHeadReports/
|
||||
├── Classes/ # Report generation logic
|
||||
│ ├── ReportBase.cs # Base class for reports
|
||||
│ ├── ExportBase.cs # Export functionality
|
||||
│ └── {ReportName}Export.cs # Specific export logic
|
||||
├── View/
|
||||
│ ├── {ReportName}InputView.xaml # Parameter input UI
|
||||
│ └── {ReportName}OutputView.xaml # Results display UI
|
||||
├── ViewModel/
|
||||
│ └── {ReportName}ViewModel.cs
|
||||
├── Resources/ # Localization
|
||||
└── {ReportName}Module.cs # Module registration
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create the Report Module Class
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/{ReportName}Module.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Media.Imaging;
|
||||
using DTS.Common;
|
||||
using DTS.Common.Interface;
|
||||
using Microsoft.Practices.Prism.Modularity;
|
||||
using Microsoft.Practices.Unity;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
[Export(typeof(IModule))]
|
||||
[Module(ModuleName = "{REPORT_NAME}Module")]
|
||||
public class {REPORT_NAME}Module : IModule
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public {REPORT_NAME}Module(IUnityContainer unityContainer)
|
||||
{
|
||||
_unityContainer = unityContainer;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_unityContainer.RegisterType<I{REPORT_NAME}InputView, {REPORT_NAME}InputView>();
|
||||
_unityContainer.RegisterType<I{REPORT_NAME}OutputView, {REPORT_NAME}OutputView>();
|
||||
_unityContainer.RegisterType<I{REPORT_NAME}ViewModel, {REPORT_NAME}ViewModel>();
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
|
||||
public class {REPORT_NAME}ImageAttribute : ImageAttribute
|
||||
{
|
||||
private BitmapImage _img;
|
||||
|
||||
public {REPORT_NAME}ImageAttribute() : this(null) { }
|
||||
|
||||
public override BitmapImage AssemblyImage
|
||||
{
|
||||
get { _img = AssemblyInfo.GetImage(AssemblyNames.DB.ToString()); return _img; }
|
||||
}
|
||||
|
||||
public {REPORT_NAME}ImageAttribute(string s)
|
||||
{
|
||||
_img = AssemblyInfo.GetImage(AssemblyNames.DB.ToString());
|
||||
}
|
||||
|
||||
public override Type GetAttributeType() => typeof(ImageAttribute);
|
||||
public override BitmapImage GetAssemblyImage() => AssemblyImage;
|
||||
|
||||
private string _name;
|
||||
public override string AssemblyName
|
||||
{
|
||||
get { _name = AssemblyNames.{REPORT_GROUP}.ToString(); return _name; }
|
||||
}
|
||||
|
||||
public override string GetAssemblyName() => AssemblyName;
|
||||
|
||||
private string _group;
|
||||
public override string AssemblyGroup
|
||||
{
|
||||
get { _group = eAssemblyGroups.Administrative.ToString(); return _group; }
|
||||
}
|
||||
|
||||
public override string GetAssemblyGroup() => AssemblyGroup;
|
||||
|
||||
public override eAssemblyRegion GetAssemblyRegion()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public override eAssemblyRegion AssemblyRegion => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create the Report Base Class
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/Classes/{ReportName}.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DTS.Common.Storage;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
public class {REPORT_NAME}Report
|
||||
{
|
||||
public string ReportTitle { get; set; }
|
||||
public DateTime GeneratedDate { get; set; }
|
||||
public List<ReportChannel> Channels { get; set; }
|
||||
|
||||
public {REPORT_NAME}Report()
|
||||
{
|
||||
Channels = new List<ReportChannel>();
|
||||
GeneratedDate = DateTime.Now;
|
||||
}
|
||||
|
||||
public void Generate(TestSetup testSetup, ReportParameters parameters)
|
||||
{
|
||||
// Validate inputs
|
||||
if (testSetup == null)
|
||||
throw new ArgumentNullException(nameof(testSetup));
|
||||
|
||||
// Generate report data
|
||||
ProcessData(testSetup, parameters);
|
||||
}
|
||||
|
||||
private void ProcessData(TestSetup testSetup, ReportParameters parameters)
|
||||
{
|
||||
// Implementation specific to report type
|
||||
}
|
||||
}
|
||||
|
||||
public class ReportChannel
|
||||
{
|
||||
public string ChannelName { get; set; }
|
||||
public double PeakValue { get; set; }
|
||||
public double Duration { get; set; }
|
||||
}
|
||||
|
||||
public class ReportParameters
|
||||
{
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime EndTime { get; set; }
|
||||
public double Threshold { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create the Export Class
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/Classes/{ReportName}Export.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using DTS.Common.Storage;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
public class {REPORT_NAME}Export : ExportBase
|
||||
{
|
||||
public {REPORT_NAME}Report Report { get; set; }
|
||||
|
||||
public void ExportToCSV(string filePath)
|
||||
{
|
||||
if (Report == null)
|
||||
throw new InvalidOperationException("Report not generated");
|
||||
|
||||
using (var writer = new StreamWriter(filePath))
|
||||
{
|
||||
WriteHeader(writer);
|
||||
WriteData(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExportToExcel(string filePath)
|
||||
{
|
||||
// Excel export implementation
|
||||
}
|
||||
|
||||
private void WriteHeader(StreamWriter writer)
|
||||
{
|
||||
writer.WriteLine($"Report,{Report.ReportTitle}");
|
||||
writer.WriteLine($"Generated,{Report.GeneratedDate:yyyy-MM-dd HH:mm:ss}");
|
||||
writer.WriteLine();
|
||||
writer.WriteLine("Channel,Peak Value,Duration");
|
||||
}
|
||||
|
||||
private void WriteData(StreamWriter writer)
|
||||
{
|
||||
foreach (var channel in Report.Channels)
|
||||
{
|
||||
writer.WriteLine($"{channel.ChannelName},{channel.PeakValue},{channel.Duration}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Input View (XAML)
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/View/{ReportName}InputView.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="{REPORT_NAME}.{REPORT_NAME}InputView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:{REPORT_NAME}.Resources">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{x:Static resx:StringResources.ReportParameters}"
|
||||
Style="{StaticResource HeaderStyle}"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Vertical" Margin="5">
|
||||
<DatePicker SelectedDate="{Binding StartDate, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.StartDate}"/>
|
||||
<DatePicker SelectedDate="{Binding EndDate, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.EndDate}"/>
|
||||
<TextBox Text="{Binding Threshold, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.Threshold}"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Row="2" Content="{x:Static resx:StringResources.Generate}"
|
||||
Command="{Binding GenerateCommand}"
|
||||
HorizontalAlignment="Right" Margin="5"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
### 5. Create Output View (XAML)
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/View/{ReportName}OutputView.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="{REPORT_NAME}.{REPORT_NAME}OutputView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:{REPORT_NAME}.Resources">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{Binding ReportTitle}"
|
||||
Style="{StaticResource HeaderStyle}"/>
|
||||
|
||||
<DataGrid Grid.Row="1" ItemsSource="{Binding ReportData}"
|
||||
AutoGenerateColumns="False" IsReadOnly="True">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Channel" Binding="{Binding ChannelName}"/>
|
||||
<DataGridTextColumn Header="Peak Value" Binding="{Binding PeakValue}"/>
|
||||
<DataGridTextColumn Header="Duration" Binding="{Binding Duration}"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Content="{x:Static resx:StringResources.ExportCSV}"
|
||||
Command="{Binding ExportCSVCommand}" Margin="5"/>
|
||||
<Button Content="{x:Static resx:StringResources.ExportExcel}"
|
||||
Command="{Binding ExportExcelCommand}" Margin="5"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
### 6. Create the ViewModel
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/ViewModel/{ReportName}ViewModel.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Input;
|
||||
using DTS.Common.Interface;
|
||||
using Microsoft.Practices.Prism.Commands;
|
||||
using Microsoft.Practices.Unity;
|
||||
|
||||
namespace {REPORT_NAME}
|
||||
{
|
||||
public class {REPORT_NAME}ViewModel : I{REPORT_NAME}ViewModel, INotifyPropertyChanged
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public I{REPORT_NAME}InputView InputView { get; set; }
|
||||
public I{REPORT_NAME}OutputView OutputView { get; set; }
|
||||
|
||||
private DateTime _startDate;
|
||||
public DateTime StartDate
|
||||
{
|
||||
get => _startDate;
|
||||
set { _startDate = value; OnPropertyChanged(nameof(StartDate)); }
|
||||
}
|
||||
|
||||
private DateTime _endDate;
|
||||
public DateTime EndDate
|
||||
{
|
||||
get => _endDate;
|
||||
set { _endDate = value; OnPropertyChanged(nameof(EndDate)); }
|
||||
}
|
||||
|
||||
private double _threshold;
|
||||
public double Threshold
|
||||
{
|
||||
get => _threshold;
|
||||
set { _threshold = value; OnPropertyChanged(nameof(Threshold)); }
|
||||
}
|
||||
|
||||
public ObservableCollection<ReportChannel> ReportData { get; set; }
|
||||
|
||||
public ICommand GenerateCommand { get; }
|
||||
public ICommand ExportCSVCommand { get; }
|
||||
public ICommand ExportExcelCommand { get; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public {REPORT_NAME}ViewModel(
|
||||
I{REPORT_NAME}InputView inputView,
|
||||
I{REPORT_NAME}OutputView outputView,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
InputView = inputView;
|
||||
InputView.DataContext = this;
|
||||
OutputView = outputView;
|
||||
OutputView.DataContext = this;
|
||||
_unityContainer = unityContainer;
|
||||
|
||||
ReportData = new ObservableCollection<ReportChannel>();
|
||||
|
||||
GenerateCommand = new DelegateCommand(OnGenerate);
|
||||
ExportCSVCommand = new DelegateCommand(OnExportCSV);
|
||||
ExportExcelCommand = new DelegateCommand(OnExportExcel);
|
||||
|
||||
InitializeDefaults();
|
||||
}
|
||||
|
||||
private void InitializeDefaults()
|
||||
{
|
||||
StartDate = DateTime.Now.AddDays(-7);
|
||||
EndDate = DateTime.Now;
|
||||
Threshold = 0.0;
|
||||
}
|
||||
|
||||
private void OnGenerate()
|
||||
{
|
||||
// Generate report logic
|
||||
var report = new {REPORT_NAME}Report();
|
||||
// ... generate data
|
||||
}
|
||||
|
||||
private void OnExportCSV()
|
||||
{
|
||||
// Export to CSV
|
||||
}
|
||||
|
||||
private void OnExportExcel()
|
||||
{
|
||||
// Export to Excel
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Add Localization Resources
|
||||
**File:** `DataPRO/Modules/Reports/{ReportName}/Resources/StringResources.resx`
|
||||
|
||||
Add required strings:
|
||||
- `ReportParameters`
|
||||
- `StartDate`
|
||||
- `EndDate`
|
||||
- `Threshold`
|
||||
- `Generate`
|
||||
- `ExportCSV`
|
||||
- `ExportExcel`
|
||||
|
||||
## For DTS Viewer Reports
|
||||
If creating a Viewer report, use this location:
|
||||
```
|
||||
DTS Viewer/DTS.Viewer.Reports/DTS.Viewer.{ReportName}/
|
||||
```
|
||||
|
||||
The module class differs slightly:
|
||||
```csharp
|
||||
[Module(ModuleName = "{REPORT_NAME}")]
|
||||
public class {REPORT_NAME}Module : I{REPORT_NAME}Module
|
||||
{
|
||||
public bool SessionStarted { get; private set; }
|
||||
|
||||
public void StartSession()
|
||||
{
|
||||
var eventAggregator = _unityContainer.Resolve<IEventAggregator>();
|
||||
eventAggregator.GetEvent<LoadViewModulEvent>().Publish(new LoadViewModulArg());
|
||||
SessionStarted = true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `{ReportName}/{ReportName}Module.cs` | Create |
|
||||
| `{ReportName}/Classes/{ReportName}.cs` | Create |
|
||||
| `{ReportName}/Classes/{ReportName}Export.cs` | Create |
|
||||
| `{ReportName}/View/{ReportName}InputView.xaml` | Create |
|
||||
| `{ReportName}/View/{ReportName}InputView.xaml.cs` | Create |
|
||||
| `{ReportName}/View/{ReportName}OutputView.xaml` | Create |
|
||||
| `{ReportName}/View/{ReportName}OutputView.xaml.cs` | Create |
|
||||
| `{ReportName}/ViewModel/{ReportName}ViewModel.cs` | Create |
|
||||
| `{ReportName}/Resources/StringResources.resx` | Create |
|
||||
| `DTS.Common/Interface/{ReportName}Interfaces.cs` | Create |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Module registered with `[Module]` attribute
|
||||
- [ ] Assembly image attribute defined
|
||||
- [ ] Input/Output views follow naming convention
|
||||
- [ ] ViewModel implements `INotifyPropertyChanged`
|
||||
- [ ] Commands use `DelegateCommand` from Prism
|
||||
- [ ] Export methods handle file I/O properly
|
||||
- [ ] Localization strings for all UI text
|
||||
- [ ] Report generation validates inputs
|
||||
- [ ] Error handling implemented
|
||||
- [ ] Assembly group set appropriately (`eAssemblyGroups.Administrative`)
|
||||
|
||||
## Common Patterns
|
||||
|
||||
1. **Two-View Pattern:** Reports use separate Input and Output views
|
||||
2. **ExportBase Inheritance:** Export classes inherit from `ExportBase`
|
||||
3. **ObservableCollection:** Use for data binding in ViewModels
|
||||
4. **DelegateCommand:** Use Prism's `DelegateCommand` for ICommand implementation
|
||||
244
GLM5Analysis/PromptTemplates/AddNewSensorType.md
Normal file
244
GLM5Analysis/PromptTemplates/AddNewSensorType.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# Add New Sensor Type - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO manages sensor configurations through the `DataPRO/Modules/SensorsList/` module. The system supports various sensor types (Analog, Digital I/O, Squib, UART, Stream) with a consistent MVVM architecture using Prism modularity and Unity dependency injection.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
DataPRO/Modules/SensorsList/
|
||||
├── SensorsList/ # Main sensor list management
|
||||
│ ├── Model/ # Sensor data models
|
||||
│ ├── View/ # XAML views
|
||||
│ ├── ViewModel/ # Business logic
|
||||
│ ├── Resources/ # Localization
|
||||
│ └── SensorsListModule.cs # Module registration
|
||||
├── SensorSettingsModule/ # Sensor configuration UI
|
||||
└── SoftwareFilters/ # Signal processing filters
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create the Sensor Model
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/Model/{SensorName}Setting.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DTS.Common.Classes.Sensors;
|
||||
using DTS.Common.Enums.Sensors;
|
||||
|
||||
namespace SensorsList.Model
|
||||
{
|
||||
public class {SENSOR_NAME}Setting : ISensorSetting
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string SerialNumber { get; set; }
|
||||
public string ChannelCode { get; set; }
|
||||
|
||||
// Add sensor-specific properties
|
||||
public double CalibrationFactor { get; set; }
|
||||
public string EngineeringUnits { get; set; }
|
||||
|
||||
// Required interface members
|
||||
public KnownChannelTypes ChannelType => KnownChannelTypes.{CHANNEL_TYPE};
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
throw new ArgumentException("Name is required");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create the View (XAML)
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/View/{SensorName}View.xaml`
|
||||
|
||||
```xml
|
||||
<UserControl x:Class="SensorsList.View.{SENSOR_NAME}View"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:resx="clr-namespace:SensorsList.Resources"
|
||||
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="{x:Static resx:StringResources.{SENSOR_NAME}_Header}"
|
||||
Style="{StaticResource HeaderStyle}"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Orientation="Vertical">
|
||||
<TextBox Text="{Binding {SENSOR_NAME}Name, Mode=TwoWay}"
|
||||
Header="{x:Static resx:StringResources.Name}"/>
|
||||
<!-- Add sensor-specific controls -->
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
**Code-behind:** `DataPRO/Modules/SensorsList/SensorsList/View/{SensorName}View.xaml.cs`
|
||||
|
||||
```csharp
|
||||
using System.Windows.Controls;
|
||||
using DTS.Common.Interface.Sensors.SensorsList;
|
||||
|
||||
namespace SensorsList.View
|
||||
{
|
||||
public partial class {SENSOR_NAME}View : UserControl, I{SENSOR_NAME}View
|
||||
{
|
||||
public {SENSOR_NAME}View()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Create the ViewModel
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/ViewModel/{SensorName}ViewModel.cs`
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using DTS.Common.Interface.Sensors.SensorsList;
|
||||
using DTS.Common.Events.Sensors.SensorsList;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using Unity;
|
||||
|
||||
namespace SensorsList
|
||||
{
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class {SENSOR_NAME}ViewModel : I{SENSOR_NAME}ViewModel, INotifyPropertyChanged
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IRegionManager _regionManager;
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public I{SENSOR_NAME}View View { get; set; }
|
||||
|
||||
private ObservableCollection<{SENSOR_NAME}Setting> _items;
|
||||
public ObservableCollection<{SENSOR_NAME}Setting> Items
|
||||
{
|
||||
get => _items;
|
||||
set { _items = value; OnPropertyChanged(nameof(Items)); }
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public {SENSOR_NAME}ViewModel(
|
||||
I{SENSOR_NAME}View view,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
View.DataContext = this;
|
||||
_regionManager = regionManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
_unityContainer = unityContainer;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
Items = new ObservableCollection<{SENSOR_NAME}Setting>();
|
||||
// Subscribe to events
|
||||
_eventAggregator.GetEvent<{SENSOR_NAME}UpdatedEvent>().Subscribe(On{SENSOR_NAME}Updated);
|
||||
}
|
||||
|
||||
private void On{SENSOR_NAME}Updated({SENSOR_NAME}Setting setting)
|
||||
{
|
||||
// Handle update logic
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Interface
|
||||
**File:** `Common/DTS.Common/Interface/Sensors/SensorsList/I{SensorName}View.cs`
|
||||
|
||||
```csharp
|
||||
namespace DTS.Common.Interface.Sensors.SensorsList
|
||||
{
|
||||
public interface I{SENSOR_NAME}View
|
||||
{
|
||||
object DataContext { get; set; }
|
||||
}
|
||||
|
||||
public interface I{SENSOR_NAME}ViewModel
|
||||
{
|
||||
I{SENSOR_NAME}View View { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Update Module Registration
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/SensorsListModule.cs`
|
||||
|
||||
Add to the `Initialize()` method:
|
||||
```csharp
|
||||
_unityContainer.RegisterType<I{SENSOR_NAME}View, {SENSOR_NAME}View>();
|
||||
_unityContainer.RegisterType<I{SENSOR_NAME}ViewModel, {SENSOR_NAME}ViewModel>();
|
||||
```
|
||||
|
||||
### 6. Add Localization Strings
|
||||
**File:** `DataPRO/Modules/SensorsList/SensorsList/Resources/StringResources.resx`
|
||||
|
||||
Add entries:
|
||||
- `{SENSOR_NAME}_Header` - Display header
|
||||
- `{SENSOR_NAME}_Description` - Description text
|
||||
|
||||
### 7. Add Channel Type Enum (if new)
|
||||
**File:** `Common/DTS.Common/Enums/Sensors/KnownChannelTypes.cs`
|
||||
|
||||
```csharp
|
||||
public enum KnownChannelTypes
|
||||
{
|
||||
// Existing types...
|
||||
{SENSOR_TYPE_CODE} // Add new type
|
||||
}
|
||||
```
|
||||
|
||||
## Files to Create/Modify Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `SensorsList/Model/{SensorName}Setting.cs` | Create |
|
||||
| `SensorsList/View/{SensorName}View.xaml` | Create |
|
||||
| `SensorsList/View/{SensorName}View.xaml.cs` | Create |
|
||||
| `SensorsList/ViewModel/{SensorName}ViewModel.cs` | Create |
|
||||
| `DTS.Common/Interface/Sensors/SensorsList/I{SensorName}View.cs` | Create |
|
||||
| `SensorsList/SensorsListModule.cs` | Modify (register types) |
|
||||
| `SensorsList/Resources/StringResources.resx` | Modify (add strings) |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Model implements `ISensorSetting` interface
|
||||
- [ ] View implements corresponding interface
|
||||
- [ ] ViewModel has `[PartCreationPolicy(CreationPolicy.Shared)]` attribute
|
||||
- [ ] Types registered in module's `Initialize()` method
|
||||
- [ ] Localization strings added for all UI text
|
||||
- [ ] Property change notifications implemented
|
||||
- [ ] Event subscriptions properly managed
|
||||
- [ ] Constructor injection follows existing patterns
|
||||
- [ ] XAML uses resource references for strings (`{x:Static resx:StringResources...}`)
|
||||
- [ ] Channel type added to enum if new sensor category
|
||||
|
||||
## Common Patterns to Follow
|
||||
|
||||
1. **Naming Convention:** Use PascalCase for class names, camelCase for private fields with underscore prefix
|
||||
2. **Dependency Injection:** All dependencies injected via constructor
|
||||
3. **Events:** Use `IEventAggregator` for cross-module communication
|
||||
4. **Regions:** Register views with appropriate region (`eAssemblyRegion.SensorsListRegion`)
|
||||
5. **ReSharper Annotations:** Include `// ReSharper disable` comments at file top as needed
|
||||
460
GLM5Analysis/PromptTemplates/AddUnitTest.md
Normal file
460
GLM5Analysis/PromptTemplates/AddUnitTest.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# Add Unit Test - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO uses NUnit for unit testing with test projects in `Common/DTS.Common.Tests/`. Tests follow the Arrange-Act-Assert (AAA) pattern and use `TestCaseSource` for parameterized tests. The testing framework emphasizes clear test naming and comprehensive coverage of edge cases.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
Common/DTS.Common.Tests/
|
||||
├── ChannelTypeUtilityShould.cs # Example test file
|
||||
├── FilterClassShould.cs
|
||||
├── GroupChannelShould.cs
|
||||
├── LinearizationFormulaShould.cs
|
||||
├── NetworkUtilsShould.cs
|
||||
├── DTS.Common.Tests.csproj
|
||||
└── Properties/
|
||||
└── AssemblyInfo.cs
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create Test Class
|
||||
**File:** `Common/DTS.Common.Tests/{ClassName}Should.cs`
|
||||
|
||||
```csharp
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace DTS.Common.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class {CLASS_NAME}Should
|
||||
{
|
||||
private {CLASS_NAME} _sut; // System Under Test
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_sut = new {CLASS_NAME}();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Cleanup if needed
|
||||
_sut = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MethodName_ShouldReturnExpectedResult_WhenGivenValidInput()
|
||||
{
|
||||
// Arrange
|
||||
var input = "valid input";
|
||||
var expected = "expected output";
|
||||
|
||||
// Act
|
||||
var result = _sut.MethodName(input);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Basic Test Patterns
|
||||
|
||||
#### Simple Assertion Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void CalculateTotal_ShouldReturnSum_WhenGivenValidNumbers()
|
||||
{
|
||||
// Arrange
|
||||
var calculator = new Calculator();
|
||||
var numbers = new List<int> { 1, 2, 3, 4, 5 };
|
||||
var expected = 15;
|
||||
|
||||
// Act
|
||||
var result = calculator.CalculateTotal(numbers);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
```
|
||||
|
||||
#### Null/Empty Input Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void ParseInput_ShouldReturnEmpty_WhenPassedNull()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseInput(null);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.EqualTo(string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseInput_ShouldReturnEmpty_WhenPassedEmptyString()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseInput("");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.EqualTo(string.Empty));
|
||||
}
|
||||
```
|
||||
|
||||
#### Exception Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void Divide_ShouldThrowDivideByZeroException_WhenDivisorIsZero()
|
||||
{
|
||||
// Arrange
|
||||
var dividend = 10;
|
||||
var divisor = 0;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<DivideByZeroException>(() => _sut.Divide(dividend, divisor));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetName_ShouldThrowArgumentException_WhenNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
string name = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => _sut.SetName(name));
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Parameterized Tests
|
||||
|
||||
#### Using TestCase
|
||||
```csharp
|
||||
[TestCase(1, 2, 3)]
|
||||
[TestCase(10, 20, 30)]
|
||||
[TestCase(-5, 5, 0)]
|
||||
[TestCase(0, 0, 0)]
|
||||
public void Add_ShouldReturnCorrectSum_WhenGivenTwoNumbers(int a, int b, int expected)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.Add(a, b);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
```
|
||||
|
||||
#### Using TestCaseSource
|
||||
```csharp
|
||||
public static IEnumerable<TestCaseData> ChannelTypeTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData("AC087155.1", "AC");
|
||||
yield return new TestCaseData("DC123456.2", "DC");
|
||||
yield return new TestCaseData("TM987654.3", "TM");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ChannelTypeTestCases))]
|
||||
public void ParseChannelType_ShouldReturnCorrectType_WhenGivenValidName(
|
||||
string sensorName, string expectedType)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseChannelType(sensorName);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedType));
|
||||
}
|
||||
```
|
||||
|
||||
#### Generating Test Data from Enum
|
||||
```csharp
|
||||
public static Array GetKnownChannelTypes()
|
||||
{
|
||||
var testValuesFromEnum = new List<string>();
|
||||
var values = Enum.GetValues(typeof(KnownChannelTypes))
|
||||
.Cast<KnownChannelTypes>()
|
||||
.Select(x => x.ToString())
|
||||
.ToArray();
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
testValuesFromEnum.Add($"{value}087155.1");
|
||||
}
|
||||
|
||||
return testValuesFromEnum.ToArray();
|
||||
}
|
||||
|
||||
[TestCaseSource("GetKnownChannelTypes")]
|
||||
public void ParseSensorKnownChannelType_ShouldReturnCorrectTag_WhenPassedSensorNameWithCorrectPrefix(
|
||||
string sensorName)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = ChannelTypeUtility.ParseSensorKnownChannelType(sensorName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(Enum.IsDefined(typeof(KnownChannelTypes), result));
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Testing Async Methods
|
||||
```csharp
|
||||
[Test]
|
||||
public async Task LoadDataAsync_ShouldReturnData_WhenDataExists()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCount = 5;
|
||||
|
||||
// Act
|
||||
var result = await _sut.LoadDataAsync();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result.Count, Is.EqualTo(expectedCount));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProcessAsync_ShouldThrowException_WhenInputInvalid()
|
||||
{
|
||||
// Arrange
|
||||
var invalidInput = "";
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await _sut.ProcessAsync(invalidInput));
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Testing Events
|
||||
```csharp
|
||||
[Test]
|
||||
public void ValueChanged_ShouldRaiseEvent_WhenValueIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var eventRaised = false;
|
||||
_sut.ValueChanged += (sender, args) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
_sut.Value = 42;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(eventRaised, "ValueChanged event was not raised");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PropertyChanged_ShouldBeRaised_WhenNameChanges()
|
||||
{
|
||||
// Arrange
|
||||
var eventArgs = new List<string>();
|
||||
_sut.PropertyChanged += (sender, e) => eventArgs.Add(e.PropertyName);
|
||||
|
||||
// Act
|
||||
_sut.Name = "New Name";
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Name", eventArgs);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Testing Collections
|
||||
```csharp
|
||||
[Test]
|
||||
public void GetItems_ShouldReturnEmptyCollection_WhenNoItemsAdded()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.GetItems();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddItem_ShouldIncreaseCount_WhenItemIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var item = new Item { Id = 1, Name = "Test" };
|
||||
var initialCount = _sut.ItemCount;
|
||||
|
||||
// Act
|
||||
_sut.AddItem(item);
|
||||
|
||||
// Assert
|
||||
Assert.That(_sut.ItemCount, Is.EqualTo(initialCount + 1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_ShouldReturnItemsInOrder_WhenItemsAdded()
|
||||
{
|
||||
// Arrange
|
||||
_sut.AddItem(new Item { Id = 3 });
|
||||
_sut.AddItem(new Item { Id = 1 });
|
||||
_sut.AddItem(new Item { Id = 2 });
|
||||
|
||||
// Act
|
||||
var result = _sut.GetItems().ToList();
|
||||
|
||||
// Assert
|
||||
Assert.That(result[0].Id, Is.EqualTo(1));
|
||||
Assert.That(result[1].Id, Is.EqualTo(2));
|
||||
Assert.That(result[2].Id, Is.EqualTo(3));
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Mocking Dependencies (if needed)
|
||||
```csharp
|
||||
using Moq;
|
||||
|
||||
[TestFixture]
|
||||
public class SensorServiceShould
|
||||
{
|
||||
private Mock<ISensorRepository> _mockRepository;
|
||||
private SensorService _sut;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mockRepository = new Mock<ISensorRepository>();
|
||||
_sut = new SensorService(_mockRepository.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSensor_ShouldReturnSensor_WhenSensorExists()
|
||||
{
|
||||
// Arrange
|
||||
var sensorId = 123;
|
||||
var expectedSensor = new Sensor { Id = sensorId, Name = "Test" };
|
||||
_mockRepository.Setup(r => r.Find(sensorId)).Returns(expectedSensor);
|
||||
|
||||
// Act
|
||||
var result = _sut.GetSensor(sensorId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedSensor));
|
||||
_mockRepository.Verify(r => r.Find(sensorId), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveSensor_ShouldCallRepository_WhenSensorIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var sensor = new Sensor { Id = 1, Name = "Test" };
|
||||
|
||||
// Act
|
||||
_sut.SaveSensor(sensor);
|
||||
|
||||
// Assert
|
||||
_mockRepository.Verify(r => r.Save(sensor), Times.Once);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Naming Convention
|
||||
|
||||
Follow this pattern: `MethodName_Scenario_ExpectedResult`
|
||||
|
||||
Examples:
|
||||
- `ParseSensorName_ShouldReturnNull_WhenPassedNull`
|
||||
- `ParseSensorName_ShouldReturnEmpty_WhenPassedEmptyString`
|
||||
- `ParseSensorName_ShouldReturnCorrectTag_WhenPassedValidName`
|
||||
- `CalculateTotal_ShouldThrowException_WhenListIsNull`
|
||||
|
||||
## Common Assertions
|
||||
|
||||
```csharp
|
||||
// Equality
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
Assert.That(result, Is.Not.EqualTo(wrongValue));
|
||||
|
||||
// Null checks
|
||||
Assert.That(result, Is.Null);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
|
||||
// Boolean
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(result, Is.False);
|
||||
|
||||
// Collections
|
||||
Assert.That(collection, Is.Empty);
|
||||
Assert.That(collection, Is.Not.Empty);
|
||||
Assert.That(collection, Has.Count.EqualTo(5));
|
||||
Assert.That(collection, Does.Contain(item));
|
||||
|
||||
// Exceptions
|
||||
Assert.Throws<ArgumentException>(() => method());
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await method());
|
||||
|
||||
// String
|
||||
Assert.That(result, Does.StartWith("prefix"));
|
||||
Assert.That(result, Does.EndWith("suffix"));
|
||||
Assert.That(result, Does.Contain("substring"));
|
||||
Assert.That(result, Is.Empty);
|
||||
|
||||
// Range
|
||||
Assert.That(value, Is.InRange(1, 10));
|
||||
Assert.That(value, Is.GreaterThan(0));
|
||||
Assert.That(value, Is.LessThan(100));
|
||||
|
||||
// Type checking
|
||||
Assert.That(result, Is.TypeOf<ExpectedType>());
|
||||
Assert.That(result, Is.InstanceOf<IBaseInterface>());
|
||||
```
|
||||
|
||||
## Files to Create Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `DTS.Common.Tests/{ClassName}Should.cs` | Create |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Test class has `[TestFixture]` attribute
|
||||
- [ ] Test methods have `[Test]` attribute
|
||||
- [ ] `[SetUp]` used for test initialization
|
||||
- [ ] `[TearDown]` used for cleanup (if needed)
|
||||
- [ ] Test names follow naming convention
|
||||
- [ ] Each test has Arrange-Act-Assert sections
|
||||
- [ ] Edge cases tested (null, empty, boundary values)
|
||||
- [ ] Exception cases tested
|
||||
- [ ] Test is isolated (doesn't depend on other tests)
|
||||
- [ ] No external dependencies (use mocks/stubs)
|
||||
- [ ] Assertions are specific and meaningful
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests in project
|
||||
dotnet test Common/DTS.Common.Tests/
|
||||
|
||||
# Run specific test class
|
||||
dotnet test --filter "FullyQualifiedName~ChannelTypeUtilityShould"
|
||||
|
||||
# Run specific test method
|
||||
dotnet test --filter "FullyQualifiedName~ChannelTypeUtilityShould.ParseSensorKnownChannelType_ShouldReturnEmpty_WhenPassedNull"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **One assertion per test** (or logically related assertions)
|
||||
2. **Test behavior, not implementation**
|
||||
3. **Use meaningful test names that describe the scenario**
|
||||
4. **Keep tests independent** - no shared state between tests
|
||||
5. **Test edge cases** - null, empty, max values, boundary conditions
|
||||
6. **Don't test private methods directly** - test through public API
|
||||
7. **Use parameterized tests for similar test cases with different data**
|
||||
8. **Mock external dependencies** - database, file system, network
|
||||
452
GLM5Analysis/PromptTemplates/FixBugInViewModel.md
Normal file
452
GLM5Analysis/PromptTemplates/FixBugInViewModel.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# Fix Bug in ViewModel - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO uses the MVVM (Model-View-ViewModel) pattern with Prism framework and Unity dependency injection. ViewModels contain business logic and state management, binding to Views through XAML data binding. Common issues include property change notification problems, command binding failures, and event subscription memory leaks.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
ViewModel Pattern:
|
||||
┌─────────────┐ Data Binding ┌─────────────┐
|
||||
│ View │◄────────────────────►│ ViewModel │
|
||||
│ (XAML) │ │ (.cs) │
|
||||
└─────────────┘ └─────────────┘
|
||||
│ │
|
||||
│ Code-behind │
|
||||
▼ ▼
|
||||
┌─────────────┐ Services/Events ┌─────────────┐
|
||||
│ IView │◄───────────────────────►│ Services │
|
||||
│ Interface │ │ (DI) │
|
||||
└─────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
## Common ViewModel Issues and Solutions
|
||||
|
||||
### Issue 1: Property Change Not Reflected in UI
|
||||
|
||||
**Symptoms:**
|
||||
- Property value changes but UI doesn't update
|
||||
- No visual feedback on data changes
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Missing PropertyChanged notification
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set => _name = value; // Missing notification!
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
private string _name;
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 2: Command Not Executing
|
||||
|
||||
**Symptoms:**
|
||||
- Button click doesn't trigger action
|
||||
- Command's CanExecute always returns false
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Command not properly initialized or CanExecute not raising
|
||||
public ICommand SaveCommand { get; set; }
|
||||
|
||||
// In constructor - missing initialization
|
||||
SaveCommand = new DelegateCommand(OnSave); // Missing CanExecute check
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
// Using Prism's DelegateCommand
|
||||
public ICommand SaveCommand { get; }
|
||||
|
||||
// In constructor
|
||||
SaveCommand = new DelegateCommand(OnSave, CanSave);
|
||||
|
||||
private void OnSave()
|
||||
{
|
||||
// Save logic
|
||||
}
|
||||
|
||||
private bool CanSave()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Name) && HasChanges;
|
||||
}
|
||||
|
||||
// Call when conditions change
|
||||
private void RaiseCanExecuteChanged()
|
||||
{
|
||||
(SaveCommand as DelegateCommand)?.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
// Call after property changes that affect CanExecute
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
RaiseCanExecuteChanged(); // Update command state
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 3: Event Subscription Memory Leak
|
||||
|
||||
**Symptoms:**
|
||||
- ViewModel not garbage collected
|
||||
- Multiple event handlers executing
|
||||
- Memory usage increasing
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Not unsubscribing from events
|
||||
public MyViewModel(IEventAggregator eventAggregator)
|
||||
{
|
||||
eventAggregator.GetEvent<DataChangedEvent>().Subscribe(OnDataChanged);
|
||||
// Missing: Keep subscriber reference for unsubscribe
|
||||
}
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
using Prism.Events;
|
||||
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private SubscriptionToken _dataChangedToken;
|
||||
|
||||
public MyViewModel(IEventAggregator eventAggregator)
|
||||
{
|
||||
_eventAggregator = eventAggregator;
|
||||
|
||||
// Keep subscription token
|
||||
_dataChangedToken = _eventAggregator.GetEvent<DataChangedEvent>()
|
||||
.Subscribe(OnDataChanged, ThreadOption.PublisherThread, false,
|
||||
data => data != null);
|
||||
}
|
||||
|
||||
// Implement IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_dataChangedToken != null)
|
||||
{
|
||||
_eventAggregator.GetEvent<DataChangedEvent>().Unsubscribe(_dataChangedToken);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 4: Thread Affinity Problems
|
||||
|
||||
**Symptoms:**
|
||||
- "The calling thread cannot access this object" exception
|
||||
- UI freezing during operations
|
||||
- Data updated on wrong thread
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Updating UI-bound property from background thread
|
||||
Task.Run(() => {
|
||||
Status = "Processing..."; // Cross-thread violation
|
||||
});
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
using System.Windows.Threading;
|
||||
|
||||
// Option 1: Use Dispatcher
|
||||
Task.Run(() => {
|
||||
Application.Current.Dispatcher.Invoke(() => {
|
||||
Status = "Processing...";
|
||||
});
|
||||
});
|
||||
|
||||
// Option 2: Use ThreadOption in event subscription
|
||||
_eventAggregator.GetEvent<DataChangedEvent>()
|
||||
.Subscribe(OnDataChanged, ThreadOption.UIThread);
|
||||
|
||||
// Option 3: Use async/await properly
|
||||
public async Task LoadDataAsync()
|
||||
{
|
||||
Status = "Loading..."; // On UI thread
|
||||
var data = await _service.GetDataAsync(); // Background
|
||||
Items = new ObservableCollection<DataItem>(data); // Back on UI thread
|
||||
Status = "Complete";
|
||||
}
|
||||
```
|
||||
|
||||
### Issue 5: Collection Changes Not Notifying
|
||||
|
||||
**Symptoms:**
|
||||
- Items added to collection don't appear in UI
|
||||
- ListView/DataGrid not updating
|
||||
|
||||
**Diagnosis:**
|
||||
```csharp
|
||||
// Problem: Using List instead of ObservableCollection
|
||||
public List<SensorItem> Sensors { get; set; }
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```csharp
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
// Use ObservableCollection for UI binding
|
||||
public ObservableCollection<SensorItem> Sensors { get; }
|
||||
|
||||
public MyViewModel()
|
||||
{
|
||||
Sensors = new ObservableCollection<SensorItem>();
|
||||
}
|
||||
|
||||
// To add items from background thread:
|
||||
Application.Current.Dispatcher.Invoke(() => {
|
||||
Sensors.Add(newItem);
|
||||
});
|
||||
|
||||
// For bulk updates, clear and add range:
|
||||
public void UpdateSensors(List<SensorItem> newItems)
|
||||
{
|
||||
Sensors.Clear();
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
Sensors.Add(item);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Debugging Approach
|
||||
|
||||
### Step 1: Check Property Implementation
|
||||
```csharp
|
||||
// Add debug output to property setter
|
||||
public string Name
|
||||
{
|
||||
get => _name;
|
||||
set
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Name: {_name} -> {value}");
|
||||
if (_name != value)
|
||||
{
|
||||
_name = value;
|
||||
OnPropertyChanged(nameof(Name));
|
||||
System.Diagnostics.Debug.WriteLine($"OnPropertyChanged raised for Name");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Verify Binding in View
|
||||
```xml
|
||||
<!-- Add PresentationTraceSources for binding debug -->
|
||||
<TextBox Text="{Binding Name, Mode=TwoWay,
|
||||
diagnostics:PresentationTraceSources.TraceLevel=High}"
|
||||
xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase"/>
|
||||
```
|
||||
|
||||
### Step 3: Check DataContext
|
||||
```csharp
|
||||
// In View code-behind, verify DataContext
|
||||
public partial class MyView : UserControl
|
||||
{
|
||||
public MyView()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.DataContextChanged += (s, e) =>
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"DataContext: {e.NewValue?.GetType().Name}");
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Verify Event Subscriptions
|
||||
```csharp
|
||||
// Add debug output to event handlers
|
||||
private void OnDataChanged(DataChangedEventArgs args)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"OnDataChanged called: {args?.Data}");
|
||||
// ... handler logic
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Check Command Binding
|
||||
```csharp
|
||||
// Add debug output to command methods
|
||||
private void OnExecute()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("OnExecute called");
|
||||
}
|
||||
|
||||
private bool CanExecute()
|
||||
{
|
||||
var result = !string.IsNullOrEmpty(Name);
|
||||
System.Diagnostics.Debug.WriteLine($"CanExecute: {result}");
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## ViewModel Template Reference
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Input;
|
||||
using DTS.Common.Interface;
|
||||
using Prism.Commands;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using Unity;
|
||||
|
||||
namespace {NAMESPACE}
|
||||
{
|
||||
[PartCreationPolicy(CreationPolicy.Shared)]
|
||||
public class {VIEWMODEL_NAME} : I{VIEWMODEL_NAME}, INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IRegionManager _regionManager;
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public I{VIEW_NAME} View { get; set; }
|
||||
|
||||
#region Properties with Change Notification
|
||||
|
||||
private string _status;
|
||||
public string Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
{
|
||||
if (_status != value)
|
||||
{
|
||||
_status = value;
|
||||
OnPropertyChanged(nameof(Status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Commands
|
||||
|
||||
public ICommand SaveCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Collections
|
||||
|
||||
public ObservableCollection<Item> Items { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public {VIEWMODEL_NAME}(
|
||||
I{VIEW_NAME} view,
|
||||
IRegionManager regionManager,
|
||||
IEventAggregator eventAggregator,
|
||||
IUnityContainer unityContainer)
|
||||
{
|
||||
View = view;
|
||||
View.DataContext = this;
|
||||
_regionManager = regionManager;
|
||||
_eventAggregator = eventAggregator;
|
||||
_unityContainer = unityContainer;
|
||||
|
||||
// Initialize commands
|
||||
SaveCommand = new DelegateCommand(OnSave, CanSave);
|
||||
CancelCommand = new DelegateCommand(OnCancel);
|
||||
|
||||
// Initialize collections
|
||||
Items = new ObservableCollection<Item>();
|
||||
|
||||
// Subscribe to events
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
// Event subscriptions
|
||||
}
|
||||
|
||||
private void OnSave()
|
||||
{
|
||||
// Save implementation
|
||||
}
|
||||
|
||||
private bool CanSave()
|
||||
{
|
||||
return true; // Add validation logic
|
||||
}
|
||||
|
||||
private void OnCancel()
|
||||
{
|
||||
// Cancel implementation
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Unsubscribe from events
|
||||
// Dispose resources
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
After fixing a ViewModel bug:
|
||||
|
||||
- [ ] PropertyChanged event raised for all bound properties
|
||||
- [ ] Commands use DelegateCommand with proper CanExecute
|
||||
- [ ] RaiseCanExecuteChanged called when command state changes
|
||||
- [ ] Event subscriptions use subscription tokens
|
||||
- [ ] Unsubscribe from events in Dispose
|
||||
- [ ] ObservableCollection used for collections
|
||||
- [ ] Dispatcher used for cross-thread updates
|
||||
- [ ] Async/await used for long-running operations
|
||||
- [ ] DataContext set correctly in constructor
|
||||
- [ ] View implements corresponding interface
|
||||
|
||||
## Quick Reference: Common Patterns
|
||||
|
||||
| Pattern | Implementation |
|
||||
|---------|---------------|
|
||||
| Property Notification | `OnPropertyChanged(nameof(PropertyName))` |
|
||||
| Command | `new DelegateCommand(Execute, CanExecute)` |
|
||||
| Event Subscription | `_eventAggregator.GetEvent<T>().Subscribe(Handler)` |
|
||||
| Thread-Safe Update | `Dispatcher.Invoke(() => Property = value)` |
|
||||
| Collection | `ObservableCollection<T>` |
|
||||
| Validation | `RaiseCanExecuteChanged()` in property setters |
|
||||
96
GLM5Analysis/README.md
Normal file
96
GLM5Analysis/README.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# GLM-5 Analysis Artifacts
|
||||
|
||||
This folder contains pre-computed analysis artifacts generated by GLM-5 for use with cheaper inference models.
|
||||
|
||||
## Purpose
|
||||
|
||||
These artifacts enable cheaper models to work effectively on the DataPRO codebase by providing:
|
||||
- High-level architecture understanding
|
||||
- Reusable code patterns
|
||||
- Task-specific prompt templates
|
||||
- Test scaffolds
|
||||
|
||||
## Contents
|
||||
|
||||
### Architecture.md
|
||||
Comprehensive system architecture document including:
|
||||
- System overview
|
||||
- Solution structure
|
||||
- Architecture patterns (MVVM, Prism, Unity DI)
|
||||
- Module dependency graph
|
||||
- Data flow diagrams
|
||||
- Extension points
|
||||
- Key technologies
|
||||
- Gotchas and tech debt
|
||||
|
||||
### PatternLibrary/
|
||||
|
||||
Reusable code patterns extracted from the codebase:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `PrismModulePattern.md` | Creating new Prism modules |
|
||||
| `MVVM_Pattern.md` | View/ViewModel implementation |
|
||||
| `DataAccessPattern.md` | Database operations via DbAPI |
|
||||
| `ServicePattern.md` | Background services and hardware communication |
|
||||
| `ImportExportPattern.md` | Data import/export workflows |
|
||||
|
||||
### PromptTemplates/
|
||||
|
||||
Task-specific prompts for common development activities:
|
||||
|
||||
| File | Use Case |
|
||||
|------|----------|
|
||||
| `AddNewSensorType.md` | Adding a new sensor to the system |
|
||||
| `AddNewReport.md` | Creating a new report module |
|
||||
| `AddNewImportFormat.md` | Supporting a new data format |
|
||||
| `AddNewHardwareSupport.md` | Adding new DAS hardware support |
|
||||
| `FixBugInViewModel.md` | Debugging MVVM issues |
|
||||
| `AddUnitTest.md` | Creating unit tests |
|
||||
|
||||
### TestScaffolds/
|
||||
|
||||
Test templates for common testing scenarios.
|
||||
|
||||
## Usage with Cheaper Models
|
||||
|
||||
When using a smaller/cheaper model for development tasks:
|
||||
|
||||
1. **For architecture questions**: Include `Architecture.md` in context
|
||||
2. **For code generation**: Include relevant pattern from `PatternLibrary/`
|
||||
3. **For specific tasks**: Include relevant prompt template from `PromptTemplates/`
|
||||
4. **For testing**: Include relevant scaffold from `TestScaffolds/`
|
||||
|
||||
### Example Context Assembly
|
||||
|
||||
```
|
||||
You are working on DataPRO, a .NET WPF sensor data acquisition system.
|
||||
|
||||
Architecture overview:
|
||||
[Include Architecture.md relevant sections]
|
||||
|
||||
Relevant pattern:
|
||||
[Include pattern from PatternLibrary/]
|
||||
|
||||
Task:
|
||||
[Include prompt from PromptTemplates/]
|
||||
```
|
||||
|
||||
## Regeneration
|
||||
|
||||
These artifacts were generated on 2026-04-16. To regenerate:
|
||||
|
||||
1. Ensure enrichment is complete in `enriched-qwen3-coder-next/`
|
||||
2. Run GLM-5 analysis on the codebase
|
||||
3. Update this folder
|
||||
|
||||
## Token Budget
|
||||
|
||||
| Artifact | Approximate Size |
|
||||
|----------|------------------|
|
||||
| Architecture.md | ~16KB / ~4,000 tokens |
|
||||
| PatternLibrary/* | ~56KB / ~14,000 tokens |
|
||||
| PromptTemplates/* | ~75KB / ~19,000 tokens |
|
||||
| TestScaffolds/* | TBD |
|
||||
|
||||
Select artifacts based on your context window budget.
|
||||
227
GLM5Analysis/TestScaffolds/DatabaseTestScaffold.cs
Normal file
227
GLM5Analysis/TestScaffolds/DatabaseTestScaffold.cs
Normal file
@@ -0,0 +1,227 @@
|
||||
using System.Data;
|
||||
using NUnit.Framework;
|
||||
using NSubstitute;
|
||||
|
||||
namespace YourNamespace.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DatabaseShould
|
||||
{
|
||||
private IDbConnection _connection;
|
||||
private IDbTransaction _transaction;
|
||||
private IDbCommand _command;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_connection = Substitute.For<IDbConnection>();
|
||||
_transaction = Substitute.For<IDbTransaction>();
|
||||
_command = Substitute.For<IDbCommand>();
|
||||
|
||||
_connection.BeginTransaction().Returns(_transaction);
|
||||
_connection.CreateCommand().Returns(_command);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExecuteQuery_ReturnExpectedResults()
|
||||
{
|
||||
var reader = Substitute.For<IDataReader>();
|
||||
reader.Read().Returns(true, true, false);
|
||||
reader[0].Returns("Value1", "Value2");
|
||||
_command.ExecuteReader().Returns(reader);
|
||||
var sut = CreateSut();
|
||||
|
||||
var results = sut.ExecuteQuery("SELECT * FROM Table");
|
||||
|
||||
Assert.That(results.Count, Is.EqualTo(2));
|
||||
Assert.That(results[0], Is.EqualTo("Value1"));
|
||||
Assert.That(results[1], Is.EqualTo("Value2"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CommitTransaction_WhenOperationSucceeds()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.ExecuteInTransaction(() => { _command.ExecuteNonQuery(); });
|
||||
|
||||
_transaction.Received(1).Commit();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RollbackTransaction_WhenOperationFails()
|
||||
{
|
||||
_command.When(c => c.ExecuteNonQuery()).Do(_ => throw new System.Exception("DB Error"));
|
||||
var sut = CreateSut();
|
||||
|
||||
Assert.Throws<System.Exception>(() => sut.ExecuteInTransaction(() => _command.ExecuteNonQuery()));
|
||||
|
||||
_transaction.Received(1).Rollback();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void UseCorrectParameters_WhenExecutingCommand()
|
||||
{
|
||||
var parameter = Substitute.For<IDbDataParameter>();
|
||||
_command.CreateParameter().Returns(parameter);
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.ExecuteWithParameter("INSERT INTO Table (Col) VALUES (@val)", "@val", "TestValue");
|
||||
|
||||
_command.Received(1).Parameters.Add(Arg.Is<IDbDataParameter>(p =>
|
||||
p.ParameterName == "@val" && (string)p.Value == "TestValue"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ReturnRowCount_WhenExecutingNonQuery()
|
||||
{
|
||||
_command.ExecuteNonQuery().Returns(5);
|
||||
var sut = CreateSut();
|
||||
|
||||
var count = sut.ExecuteNonQuery("DELETE FROM Table");
|
||||
|
||||
Assert.That(count, Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void HandleNullValues_InQueryResults()
|
||||
{
|
||||
var reader = Substitute.For<IDataReader>();
|
||||
reader.Read().Returns(true, false);
|
||||
reader.IsDBNull(0).Returns(true);
|
||||
_command.ExecuteReader().Returns(reader);
|
||||
var sut = CreateSut();
|
||||
|
||||
var results = sut.ExecuteQuery("SELECT Col FROM Table");
|
||||
|
||||
Assert.That(results.Count, Is.EqualTo(1));
|
||||
Assert.That(results[0], Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CloseConnection_AfterOperation()
|
||||
{
|
||||
_connection.State.Returns(ConnectionState.Open);
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.CloseConnection();
|
||||
|
||||
_connection.Received(1).Close();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowException_WhenConnectionFails()
|
||||
{
|
||||
_connection.When(c => c.Open()).Do(_ => throw new System.Exception("Connection failed"));
|
||||
var sut = CreateSut();
|
||||
|
||||
Assert.Throws<System.Exception>(() => sut.OpenConnection());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExecuteScalar_ReturnCorrectValue()
|
||||
{
|
||||
_command.ExecuteScalar().Returns(42);
|
||||
var sut = CreateSut();
|
||||
|
||||
var result = sut.ExecuteScalar("SELECT COUNT(*) FROM Table");
|
||||
|
||||
Assert.That(result, Is.EqualTo(42));
|
||||
}
|
||||
|
||||
private DatabaseService CreateSut()
|
||||
{
|
||||
return new DatabaseService(_connection);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_connection?.Dispose();
|
||||
_command?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public class DatabaseService
|
||||
{
|
||||
private readonly IDbConnection _connection;
|
||||
|
||||
public DatabaseService(IDbConnection connection)
|
||||
{
|
||||
_connection = connection;
|
||||
}
|
||||
|
||||
public void OpenConnection() => _connection.Open();
|
||||
|
||||
public void CloseConnection()
|
||||
{
|
||||
if (_connection.State == ConnectionState.Open)
|
||||
_connection.Close();
|
||||
}
|
||||
|
||||
public List<object> ExecuteQuery(string sql)
|
||||
{
|
||||
var results = new List<object>();
|
||||
using (var command = _connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = sql;
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
results.Add(reader.IsDBNull(0) ? null : reader[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public int ExecuteNonQuery(string sql)
|
||||
{
|
||||
using (var command = _connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = sql;
|
||||
return command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
public object ExecuteScalar(string sql)
|
||||
{
|
||||
using (var command = _connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = sql;
|
||||
return command.ExecuteScalar();
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteInTransaction(Action operation)
|
||||
{
|
||||
using (var transaction = _connection.BeginTransaction())
|
||||
{
|
||||
try
|
||||
{
|
||||
operation();
|
||||
transaction.Commit();
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteWithParameter(string sql, string paramName, object value)
|
||||
{
|
||||
using (var command = _connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = sql;
|
||||
var parameter = command.CreateParameter();
|
||||
parameter.ParameterName = paramName;
|
||||
parameter.Value = value;
|
||||
command.Parameters.Add(parameter);
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
111
GLM5Analysis/TestScaffolds/ModuleTestScaffold.cs
Normal file
111
GLM5Analysis/TestScaffolds/ModuleTestScaffold.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using NUnit.Framework;
|
||||
using NSubstitute;
|
||||
using Prism.Modularity;
|
||||
using Prism.Ioc;
|
||||
using Unity;
|
||||
|
||||
namespace YourNamespace.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ModuleShould
|
||||
{
|
||||
private IUnityContainer _unityContainer;
|
||||
private IContainerRegistry _containerRegistry;
|
||||
private IContainerProvider _containerProvider;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_unityContainer = Substitute.For<IUnityContainer>();
|
||||
_containerRegistry = Substitute.For<IContainerRegistry>();
|
||||
_containerProvider = Substitute.For<IContainerProvider>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterTypes_WhenRegisterTypesIsCalled()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.RegisterTypes(_containerRegistry);
|
||||
|
||||
_containerRegistry.Received().Register(Arg.Any<Func<object>>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Initialize_WhenInitializeIsCalled()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.Initialize();
|
||||
|
||||
_unityContainer.Received().RegisterType(Arg.Any<System.Type>(), Arg.Any<System.Type>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RegisterViewAndViewModel_AsSingletons()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.Initialize();
|
||||
|
||||
_unityContainer.Received().RegisterType<ITestView, TestView>();
|
||||
_unityContainer.Received().RegisterType<ITestViewModel, TestViewModel>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnInitialized_DoesNotThrow()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
Assert.DoesNotThrow(() => sut.OnInitialized(_containerProvider));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ImplementIModuleInterface()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
Assert.That(sut, Is.InstanceOf<IModule>());
|
||||
}
|
||||
|
||||
private TestModule CreateSut()
|
||||
{
|
||||
return new TestModule(_unityContainer);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TestModule : IModule
|
||||
{
|
||||
private readonly IUnityContainer _unityContainer;
|
||||
|
||||
public TestModule(IUnityContainer unityContainer)
|
||||
{
|
||||
_unityContainer = unityContainer;
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_unityContainer.RegisterType<ITestView, TestView>();
|
||||
_unityContainer.RegisterType<ITestViewModel, TestViewModel>();
|
||||
}
|
||||
|
||||
public void OnInitialized(IContainerProvider containerProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public void RegisterTypes(IContainerRegistry containerRegistry)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public interface ITestView { }
|
||||
public class TestView : ITestView { }
|
||||
public interface ITestViewModel { }
|
||||
public class TestViewModel : ITestViewModel { }
|
||||
}
|
||||
158
GLM5Analysis/TestScaffolds/ServiceTestScaffold.cs
Normal file
158
GLM5Analysis/TestScaffolds/ServiceTestScaffold.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using NSubstitute;
|
||||
|
||||
namespace YourNamespace.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ServiceShould
|
||||
{
|
||||
private IServiceDependency _dependency;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_dependency = Substitute.For<IServiceDependency>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReturnResult_WhenOperationCompletes()
|
||||
{
|
||||
var expected = new ServiceResult { Value = 42 };
|
||||
_dependency.GetDataAsync().Returns(Task.FromResult(expected));
|
||||
var sut = CreateSut();
|
||||
|
||||
var result = await sut.PerformOperationAsync();
|
||||
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Value, Is.EqualTo(42));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CallDependency_WhenOperationIsPerformed()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
await sut.PerformOperationAsync();
|
||||
|
||||
await _dependency.Received(1).GetDataAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InvokeCallback_WhenOperationCompletes()
|
||||
{
|
||||
var callbackInvoked = false;
|
||||
var sut = CreateSut();
|
||||
sut.OnCompleted += () => callbackInvoked = true;
|
||||
|
||||
await sut.PerformOperationAsync();
|
||||
|
||||
Assert.That(callbackInvoked, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task InvokeCallbackWithError_WhenOperationFails()
|
||||
{
|
||||
_dependency.GetDataAsync().Returns(Task.FromException<ServiceResult>(new System.Exception("Test error")));
|
||||
var errorCallbackInvoked = false;
|
||||
var sut = CreateSut();
|
||||
sut.OnError += (ex) => errorCallbackInvoked = true;
|
||||
|
||||
try
|
||||
{
|
||||
await sut.PerformOperationAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
Assert.That(errorCallbackInvoked, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ThrowException_WhenNotInitialized()
|
||||
{
|
||||
var sut = new TestService(null);
|
||||
|
||||
Assert.ThrowsAsync<System.InvalidOperationException>(() => sut.PerformOperationAsync());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CancelOperation_WhenCancellationTokenIsCancelled()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
var sut = CreateSut();
|
||||
|
||||
Assert.ThrowsAsync<OperationCanceledException>(() => sut.PerformOperationAsync(cts.Token));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SupportMultipleConcurrentOperations()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
var tasks = Enumerable.Range(0, 5)
|
||||
.Select(_ => sut.PerformOperationAsync())
|
||||
.ToArray();
|
||||
|
||||
var results = await Task.WhenAll(tasks);
|
||||
|
||||
Assert.That(results.Length, Is.EqualTo(5));
|
||||
Assert.That(results.All(r => r != null), Is.True);
|
||||
}
|
||||
|
||||
private TestService CreateSut()
|
||||
{
|
||||
return new TestService(_dependency);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public interface IServiceDependency
|
||||
{
|
||||
Task<ServiceResult> GetDataAsync();
|
||||
}
|
||||
|
||||
public class ServiceResult
|
||||
{
|
||||
public int Value { get; set; }
|
||||
}
|
||||
|
||||
public class TestService
|
||||
{
|
||||
private readonly IServiceDependency _dependency;
|
||||
public event Action OnCompleted;
|
||||
public event Action<System.Exception> OnError;
|
||||
|
||||
public TestService(IServiceDependency dependency)
|
||||
{
|
||||
_dependency = dependency;
|
||||
}
|
||||
|
||||
public async Task<ServiceResult> PerformOperationAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_dependency == null)
|
||||
throw new System.InvalidOperationException("Service not initialized");
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _dependency.GetDataAsync();
|
||||
OnCompleted?.Invoke();
|
||||
return result;
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
OnError?.Invoke(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
133
GLM5Analysis/TestScaffolds/ViewModelTestScaffold.cs
Normal file
133
GLM5Analysis/TestScaffolds/ViewModelTestScaffold.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using System.ComponentModel;
|
||||
using NUnit.Framework;
|
||||
using NSubstitute;
|
||||
using Prism.Events;
|
||||
using Prism.Regions;
|
||||
using Unity;
|
||||
|
||||
namespace YourNamespace.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ViewModelShould
|
||||
{
|
||||
private IEventAggregator _eventAggregator;
|
||||
private IRegionManager _regionManager;
|
||||
private IUnityContainer _unityContainer;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_eventAggregator = Substitute.For<IEventAggregator>();
|
||||
_regionManager = Substitute.For<IRegionManager>();
|
||||
_unityContainer = Substitute.For<IUnityContainer>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RaisePropertyChanged_WhenPropertyIsSet()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
var eventArgs = new List<PropertyChangedEventArgs>();
|
||||
sut.PropertyChanged += (sender, e) => eventArgs.Add(e);
|
||||
|
||||
sut.SomeProperty = "NewValue";
|
||||
|
||||
Assert.That(eventArgs.Count, Is.EqualTo(1));
|
||||
Assert.That(eventArgs[0].PropertyName, Is.EqualTo(nameof(sut.SomeProperty)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NotRaisePropertyChanged_WhenPropertyIsSetToSameValue()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
sut.SomeProperty = "Value";
|
||||
var eventArgs = new List<PropertyChangedEventArgs>();
|
||||
sut.PropertyChanged += (sender, e) => eventArgs.Add(e);
|
||||
|
||||
sut.SomeProperty = "Value";
|
||||
|
||||
Assert.That(eventArgs.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExecuteCommand_WhenCommandIsInvoked()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.SomeCommand.Execute(null);
|
||||
|
||||
Assert.That(sut.SomeCommandWasExecuted, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PublishEvent_WhenActionIsPerformed()
|
||||
{
|
||||
var testEvent = Substitute.For<TestEvent>();
|
||||
_eventAggregator.GetEvent<TestEvent>().Returns(testEvent);
|
||||
var sut = CreateSut();
|
||||
|
||||
sut.PerformAction();
|
||||
|
||||
testEvent.Received(1).Publish(Arg.Any<TestPayload>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SubscribeToEvents_WhenInitialized()
|
||||
{
|
||||
var testEvent = Substitute.For<TestEvent>();
|
||||
_eventAggregator.GetEvent<TestEvent>().Returns(testEvent);
|
||||
|
||||
var sut = CreateSut();
|
||||
|
||||
testEvent.Received(1).Subscribe(Arg.Any<Action<TestPayload>>());
|
||||
}
|
||||
|
||||
private TestViewModel CreateSut()
|
||||
{
|
||||
return new TestViewModel(_regionManager, _eventAggregator, _unityContainer);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TestViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
private string _someProperty;
|
||||
public string SomeProperty
|
||||
{
|
||||
get => _someProperty;
|
||||
set
|
||||
{
|
||||
if (_someProperty != value)
|
||||
{
|
||||
_someProperty = value;
|
||||
OnPropertyChanged(nameof(SomeProperty));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DelegateCommand SomeCommand { get; }
|
||||
public bool SomeCommandWasExecuted { get; private set; }
|
||||
|
||||
public TestViewModel(IRegionManager regionManager, IEventAggregator eventAggregator, IUnityContainer unityContainer)
|
||||
{
|
||||
SomeCommand = new DelegateCommand(() => SomeCommandWasExecuted = true);
|
||||
}
|
||||
|
||||
public void PerformAction() { }
|
||||
protected void OnPropertyChanged(string propertyName) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public class TestEvent : PubSubEvent<TestPayload> { }
|
||||
public class TestPayload { }
|
||||
public class DelegateCommand
|
||||
{
|
||||
private readonly Action _execute;
|
||||
public DelegateCommand(Action execute) => _execute = execute;
|
||||
public void Execute(object parameter) => _execute();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user