Denis
Denis
CC#
Created by Denis on 6/5/2024 in #help
Unit Testing gRPC server with EF Core
I have an ASP.NET Core gRPC server that exposes several endpoints. Calling these endpoints affects a connected database via EF Core. How can I effectively test the gRPC endpoints while also mocking the database? The db is abstracted through an interface. Let's say my endpoints are: - CreateGreeting, and - GetAllGreetings
1 replies
CC#
Created by Denis on 5/27/2024 in #help
✅ Setting up WPF logging to OTLP collector
My docker compose:
version: '3'

services:
loki:
container_name: "loki"
image: grafana/loki:latest
ports:
- "3100:3100"
networks:
- telemetry

grafana:
container_name: "grafana"
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
networks:
- telemetry
depends_on:
- loki

collector:
container_name: "collector"
hostname: "collector"
image: otel/opentelemetry-collector:latest
command: '--config=/etc/otelcol/otel-collector-config.yml'
volumes:
- ./configs:/etc/otelcol
ports:
- "8888:8888"
- "8889:8889"
- "4317:4317"
- "4318:4318"
networks:
- telemetry

networks:
telemetry:
driver: bridge
external: true
version: '3'

services:
loki:
container_name: "loki"
image: grafana/loki:latest
ports:
- "3100:3100"
networks:
- telemetry

grafana:
container_name: "grafana"
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
networks:
- telemetry
depends_on:
- loki

collector:
container_name: "collector"
hostname: "collector"
image: otel/opentelemetry-collector:latest
command: '--config=/etc/otelcol/otel-collector-config.yml'
volumes:
- ./configs:/etc/otelcol
ports:
- "8888:8888"
- "8889:8889"
- "4317:4317"
- "4318:4318"
networks:
- telemetry

networks:
telemetry:
driver: bridge
external: true
My otel config:
receivers:
otlp:
protocols:
grpc:
endpoint: localhost:4317
http:
endpoint: localhost:4318
processors:
batch:
send_batch_size: 4
timeout: 10s
exporters:
otlphttp:
endpoint: "http://loki:3100/otlp/v1/logs"
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]
receivers:
otlp:
protocols:
grpc:
endpoint: localhost:4317
http:
endpoint: localhost:4318
processors:
batch:
send_batch_size: 4
timeout: 10s
exporters:
otlphttp:
endpoint: "http://loki:3100/otlp/v1/logs"
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlphttp]
My WPF app config
services.AddLogging(builder =>
builder.AddOpenTelemetry(options =>
options.SetResourceBuilder(
ResourceBuilder
.CreateDefault()
.AddService(APP_NAME))
.AddConsoleExporter()
.AddOtlpExporter(o =>
{
o.Endpoint = new Uri("http://localhost:4318/v1/logs");
o.Protocol = OtlpExportProtocol.HttpProtobuf;
})));
services.AddLogging(builder =>
builder.AddOpenTelemetry(options =>
options.SetResourceBuilder(
ResourceBuilder
.CreateDefault()
.AddService(APP_NAME))
.AddConsoleExporter()
.AddOtlpExporter(o =>
{
o.Endpoint = new Uri("http://localhost:4318/v1/logs");
o.Protocol = OtlpExportProtocol.HttpProtobuf;
})));
But... no logs are received by the collector. Why and how do I fix it? Or at least the running collector instance isn't logging anything when my app is producing logs.
14 replies
CC#
Created by Denis on 5/22/2024 in #help
✅ Setting up centralized logging for desktop apps
I wish to setup centralized logging using OpenTelemetry. I'm lost on exporting structured logs from a desktop application. I have a .NET Server managing database access that exposes gRPC endpoints for clients. I have .NET desktop Clients (specifically WPF, but this should be irrelevant) that connect to the server via gRPC. The server and clients shall be installed on-premises; serving these as a cloud SaaS is a future prospect. I've managed to setup opentelemetry logging for my WPF client; however, I'm clueless as to where and how I should export the collected data. From my understanding, logs from both the server and clients should be exported to a third service specifically for handling logs. Is this a good approach? My initial idea was to send logs from clients to the server and then have the server handle the dirty work. It stemmed from the fact that most guides on setting up OT are for ASP.NET Core applications, that just add an endpoint for sending the collected logs and traces. To what "system" or service should I export my logs to? I wish to keep it small and simple, handling around 20 clients and a server and storing logs and metrics for 30 days.
23 replies
CC#
Created by Denis on 4/24/2024 in #help
✅ Anybody using PostSharp/Metalama?
Before source generators and MVVM toolkit were around, I was looking for ways to simplify my mvvm development. I came across PostSharp. It was ok to a degree, but the pricing was insane. Now we have source generators. Aaand, so do they - metalama. Is anybody using their products? Do you see any benefits? I didn't see a huge advantage then, and I definitely don't see any point in their products now. Especially given the price.
10 replies
CC#
Created by Denis on 3/27/2024 in #help
✅ Is it possible to explicitly state that a method wont modify a mutable class?
Assume the following class:
public sealed class MyTest
{
public string SomeString { get; set; }
}
public sealed class MyTest
{
public string SomeString { get; set; }
}
I have an instance of said class, and I wish to process it via some method. I want to explicitly state, that this method will not mutate the class instance. is that possible? E.g., method:
public static string PureReadingMethod(MyTest instance)
=> instance.SomeString + "_Read";
public static string PureReadingMethod(MyTest instance)
=> instance.SomeString + "_Read";
I've looked into freezables, but it doesn't seem like this is relevant for my case. I'd like to make it clear that: - I'm aware of read-only properties, I need to keep the property accessors unchanged - I'm aware of the possibility to pass only the single property value as the method parameter and that strings are immutable, I have more complex classes and I cannot pass each property separately Thank you for your help!
5 replies
CC#
Created by Denis on 3/14/2024 in #help
Bogus hierarchical data
Given I have the following class definition
public sealed class Project
{
/// <summary>
/// Unique project identifier
/// </summary>
public required Guid Id { get; init; }

/// <summary>
/// Project name
/// </summary>
public required string Name { get; init; }

/// <summary>
/// Project parent
/// </summary>
public Project? Parent { get; init; }

/// <summary>
/// Sub-projects
/// </summary>
public IReadOnlyCollection<Project> SubProjects { get; init; } = Array.Empty<Project>();
}
public sealed class Project
{
/// <summary>
/// Unique project identifier
/// </summary>
public required Guid Id { get; init; }

/// <summary>
/// Project name
/// </summary>
public required string Name { get; init; }

/// <summary>
/// Project parent
/// </summary>
public Project? Parent { get; init; }

/// <summary>
/// Sub-projects
/// </summary>
public IReadOnlyCollection<Project> SubProjects { get; init; } = Array.Empty<Project>();
}
I wish to generate test instances of Project using Bogus (https://github.com/bchavez/Bogus). So, I define a faker like so:
var projectFaker = new Faker<Project>()
.RuleFor(document => document.Id, faker => faker.Random.Guid())
.RuleFor(document => document.Name, faker => faker.System.FileName())
var projectFaker = new Faker<Project>()
.RuleFor(document => document.Id, faker => faker.Random.Guid())
.RuleFor(document => document.Name, faker => faker.System.FileName())
But how do I fake the Parent property and the SubProjects property?
2 replies
CC#
Created by Denis on 1/18/2024 in #help
✅ Best practices for WPF context aware documentation
All projects in the company I work at are made in WinForms .NET 4.8 and provide CHM help file documentation. The new project I'm working on is in WPF .NET 8. And for whatever reason I'm unable to reference the System.Windows.Forms reference to access the Help class necessary for opening the CHMs. Yes, I can do process open, but I'd love to be able to open the documentation on a specific page, based on context. So, I've been thinking whether there is a better alternative for application help documentation. Is there? What would you recommend
45 replies
CC#
Created by Denis on 1/14/2024 in #help
Validating XML files in CSPROJ
I have multiple XML files as project items. These files are filled by developers and are consumed on build by a source generator. A dev can make a typo in the XML and the generator will fail. Yes, it is possible to introduce XML validation into the generator and outout diagnostics that specify the location of the error. But, is it possible to do static XML validation via some XSD while it being edited from Visual Studio/Rider without relying on the generator diagnostics? So that the dev has instant feedback on what they are doing wrong, before they even compile.
10 replies
CC#
Created by Denis on 12/28/2023 in #help
✅ Execute ICommand while button is pressed
In my WPF view I have a 9x9 grid of buttons representing movement controls: up, down, left, right, home. I wish to press and hold such a button to execute a Viewmodel command until the button is released. These buttons would serve to move the viewport of a canvas. Afaik, this isn't possible directly, as a button command is invoked on click. So this will probably require some markup extension to attach to the button events directly; however, my attempts were not fruitful. How would you implement such a feature?
5 replies
CC#
Created by Denis on 12/25/2023 in #help
System.Text.Json Source Generators
The JSON serialization can be optimized using source generators. I understand that I have to write a context for the given class I wish to serialize. And I'd specify the given context when serializing the class instance. What I do not understand is how I would pass the context for other classes within the serialized class? E.g.
public record Position(double X, double Y);
public record Size(int Width, int Height);

public class CanvasSettings
{
public Position ViewportLocation { get; set; }
public Size ViewportSize { get; set; }
.....
}

[JsonSerializable(typeof(CanvasSettings))]
public partial sealed CanvasSettingsContext
: JsonSerializerContext
{
}
public record Position(double X, double Y);
public record Size(int Width, int Height);

public class CanvasSettings
{
public Position ViewportLocation { get; set; }
public Size ViewportSize { get; set; }
.....
}

[JsonSerializable(typeof(CanvasSettings))]
public partial sealed CanvasSettingsContext
: JsonSerializerContext
{
}
Is the context of the CanvasSettings including data for the its property members? How can I utilize source generators when working with polymorphic types? E.g., IAnimal, Dog : IAnimal, Cat : IAnimal.
[JsonPolymorphic, JsonDerived(typeof(Dog), nameof(Dog)), JsonDerived(typeof(Cat), nameof(Cat))]
public interface IAnimal {}
[JsonPolymorphic, JsonDerived(typeof(Dog), nameof(Dog)), JsonDerived(typeof(Cat), nameof(Cat))]
public interface IAnimal {}
Do I write a context for the interface? How would the context cover all the members of the derived classes?
3 replies
CC#
Created by Denis on 11/23/2023 in #help
WPF Custom controls/User controls binding issues and total confusion
need to create a reusable WPF control for displaying plugin settings. There is a defined set of settings, e.g., toggle, combo, file path. Each of those settings requires its own WPF control. My initial attempt was to create a User Control, and define a Dependency Property within it:
public static readonly DependencyProperty SelectedNodeProperty =
DependencyProperty.Register(
nameof(SelectedNode), // XAML attribute name
typeof(NodeViewModel), // Binding expected type
typeof(NodeSettings)); // Control name

public NodeViewModel SelectedNode
{
get => (NodeViewModel)GetValue(SelectedNodeProperty);
set => SetValue(SelectedNodeProperty, value);
}
public static readonly DependencyProperty SelectedNodeProperty =
DependencyProperty.Register(
nameof(SelectedNode), // XAML attribute name
typeof(NodeViewModel), // Binding expected type
typeof(NodeSettings)); // Control name

public NodeViewModel SelectedNode
{
get => (NodeViewModel)GetValue(SelectedNodeProperty);
set => SetValue(SelectedNodeProperty, value);
}
I set the data context in the constructor to this Initially, I've tried using IEnumerable as a type, but let's stay with this non-collection type for now. The view of the User control is:
<ItemsControl ItemsSource="{Binding SelectedNode.Settings, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="UserControl">
<StackPanel Margin="0,5,0,5">
<TextBlock Text="Bobek" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type nodeSettings:ToggleSetting}">
<local:ToggleSettingView Setting="{Binding}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
<ItemsControl ItemsSource="{Binding SelectedNode.Settings, Mode=OneWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="UserControl">
<StackPanel Margin="0,5,0,5">
<TextBlock Text="Bobek" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type nodeSettings:ToggleSetting}">
<local:ToggleSettingView Setting="{Binding}" />
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
I place my control into my MainWindow.xaml:
<controls:NodeSettings SelectedNode="{Binding SelectedNode, Mode=OneWay}" Grid.Column="1" />
<controls:NodeSettings SelectedNode="{Binding SelectedNode, Mode=OneWay}" Grid.Column="1" />
But the data is not bound!!!
2 replies
CC#
Created by Denis on 10/3/2023 in #help
❔ User Control with custom dependency properties
I'm developing a user control that is supposed to have two properties bound by a parent control that will be using it. Initially, I developed the UC like I'm used to - View + ViewModel. However, I've quickly discovered that I need custom dependency properties that must be written into the View. So, in short, I need help to correctly propagate the bound values back to the ViewModel. In my Parent ViewModel, I have a
public ObservableCollection<TemplateAttribute> Templates { get; } = new()
{
new TemplateAttribute("Height", AttributeType.Qualitative)
};
public ObservableCollection<TemplateAttribute> Templates { get; } = new()
{
new TemplateAttribute("Height", AttributeType.Qualitative)
};
In my parent view, I'm using my custom user control:
<StackPanel Grid.Row="1">
<Label Content="Attribute options" />
<views:AttributeFilterBuilderControl TemplateAttributes="{Binding Templates}" />
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="Attribute options" />
<views:AttributeFilterBuilderControl TemplateAttributes="{Binding Templates}" />
</StackPanel>
UC View-behind:
public partial class AttributeFilterBuilderControl
{
public static readonly DependencyProperty TemplateAttributesProperty =
DependencyProperty.Register(nameof(TemplateAttributes),
typeof(IEnumerable<TemplateAttribute>),
typeof(AttributeFilterBuilderControl), new PropertyMetadata(Enumerable.Empty<TemplateAttribute>()));

public IEnumerable<TemplateAttribute>? TemplateAttributes
{
get => (IEnumerable<TemplateAttribute>?)GetValue(TemplateAttributesProperty);
set => SetValue(TemplateAttributesProperty, value);
}

public AttributeFilterBuilderControl() => InitializeComponent();
}
public partial class AttributeFilterBuilderControl
{
public static readonly DependencyProperty TemplateAttributesProperty =
DependencyProperty.Register(nameof(TemplateAttributes),
typeof(IEnumerable<TemplateAttribute>),
typeof(AttributeFilterBuilderControl), new PropertyMetadata(Enumerable.Empty<TemplateAttribute>()));

public IEnumerable<TemplateAttribute>? TemplateAttributes
{
get => (IEnumerable<TemplateAttribute>?)GetValue(TemplateAttributesProperty);
set => SetValue(TemplateAttributesProperty, value);
}

public AttributeFilterBuilderControl() => InitializeComponent();
}
UC VM property:
[ObservableProperty] private IEnumerable<TemplateAttribute>? m_templates;
[ObservableProperty] private IEnumerable<TemplateAttribute>? m_templates;
UC View:
<UserControl.Style>
<Style>
<Setter Property="local:AttributeFilterBuilderControl.TemplateAttributes" Value="{Binding Templates, Mode=OneWayToSource}" />
</Style>
</UserControl.Style>
<UserControl.Style>
<Style>
<Setter Property="local:AttributeFilterBuilderControl.TemplateAttributes" Value="{Binding Templates, Mode=OneWayToSource}" />
</Style>
</UserControl.Style>
From my debugging, it seems that the Parent Templates property getter is never called, and naturally, the setter of the dependency property is also never called. Any tips?
8 replies
CC#
Created by Denis on 9/25/2023 in #help
❔ Is it possible to log for the user (not dev) with localization?
Given that a multi-lingual app exists And it is currently configured to some language When something is logged via ILogger Then the log message is written in the configured language Is this possible? If so, how?
8 replies
CC#
Created by Denis on 9/1/2023 in #help
✅ How to avoid blocking until all subscribed handlers complete handling an event
I have the following code:
public sealed class MessageBroker
{
private readonly ConcurrentDictionary<string, byte> m_sentMessages = new();
private readonly Action<MessageMetadata, string> m_onNewMessage;
private readonly FileSystemWatcher m_watcher = new();

public MessageBroker(string exchangePath, Action<MessageMetadata, string> onNewMessage)
{
m_onNewMessage = onNewMessage ?? throw new ArgumentNullException(nameof(onNewMessage));

m_watcher.Path = exchangePath;
m_watcher.Filter = "[*]_[*]_[*]_[*]_[*]";
m_watcher.EnableRaisingEvents = true;
m_watcher.IncludeSubdirectories = false;
m_watcher.InternalBufferSize = 16_384;
m_watcher.Created += WatcherOnCreated;
}

private static async ValueTask WaitForFileAsync(string filePath)
{
// Some Async method
}

private async ValueTask ProcessNewMessageAsync(string fullPath)
{
if (m_sentMessages.TryRemove(fullPath, out var _))
return;
if (!MessageHelper.TryParseToMetadata(fullPath, out var metadata) || metadata is null)
return;

await WaitForFileAsync(fullPath).ConfigureAwait(false);

m_onNewMessage(metadata.Value, fullPath);
}

private async void WatcherOnCreated(object sender, FileSystemEventArgs e)
=> await ProcessNewMessageAsync(e.FullPath).ConfigureAwait(false);
}
public sealed class MessageBroker
{
private readonly ConcurrentDictionary<string, byte> m_sentMessages = new();
private readonly Action<MessageMetadata, string> m_onNewMessage;
private readonly FileSystemWatcher m_watcher = new();

public MessageBroker(string exchangePath, Action<MessageMetadata, string> onNewMessage)
{
m_onNewMessage = onNewMessage ?? throw new ArgumentNullException(nameof(onNewMessage));

m_watcher.Path = exchangePath;
m_watcher.Filter = "[*]_[*]_[*]_[*]_[*]";
m_watcher.EnableRaisingEvents = true;
m_watcher.IncludeSubdirectories = false;
m_watcher.InternalBufferSize = 16_384;
m_watcher.Created += WatcherOnCreated;
}

private static async ValueTask WaitForFileAsync(string filePath)
{
// Some Async method
}

private async ValueTask ProcessNewMessageAsync(string fullPath)
{
if (m_sentMessages.TryRemove(fullPath, out var _))
return;
if (!MessageHelper.TryParseToMetadata(fullPath, out var metadata) || metadata is null)
return;

await WaitForFileAsync(fullPath).ConfigureAwait(false);

m_onNewMessage(metadata.Value, fullPath);
}

private async void WatcherOnCreated(object sender, FileSystemEventArgs e)
=> await ProcessNewMessageAsync(e.FullPath).ConfigureAwait(false);
}
The event handler WatcherOnCreated should have very short execution time to avoid overflowing the FileSystemWatcher buffer. I'd like to ask what is the most optimal way of avoiding blocking the FSW when handling the event? I suppose it is running the handler using async void, but from what I understand the execution is blocked until the WaitForFileAsync is awaited, right? Can I await Task.Yield() in the beginning of ProcessNewMessageAsync to avoid blocking sooner?
13 replies
CC#
Created by Denis on 8/30/2023 in #help
✅ Correct MVVM approach for displaying models representing settings
Given that I have a set of models representing settings, When I supply then to a UI utilizing MVVM, And display each settings with a respective UI component, Then the user can change the given setting And the change will be reflected in the model. As far as I've understood, Models shouldn't really be reactive, i.e., shouldn't implement the INotifyPropertyChanged interface. This goes inline with the motive to keep models very simple, containing primarily properties. Assume that I have generic models representing, e.g., a Toggle, Text input, Number input, and a Date input settings. They share the same interface ISettingsElement, and are exposed to the View via the respective ViewModel. Displaying specific UI elements based on the provided model is pretty easy. But how do I make sure that the model values are two-way-bound to the UI elements? E.g., I wish to display my collection of settings, and provide the user with a Clear or Defaults button. The ViewModel would naturally expose respective ICommand implementations to do exactly that. But... going through the Models and modifying their configured values won't be reflected in the UI. Or, is there a non-hacky way to tell the parent UI control to refresh itself? All this seems a bit hacky and limiting, when trying to do clean MVVM... Hopefully, you'll be able to provide me the missing puzzle piece for correctly understading MVVM and implementing the settings feature.
115 replies
CC#
Created by Denis on 8/23/2023 in #help
❔ Quality and free project planning tools
Are there any tools you could recommend for creating and managing use cases, process diagrams, etc? Preferably free/open source or for a reasonable price. I currently stupidly rely on word and plantuml :/ pretty horrible for documenting a project I have some experience with Enterprise architect from university, and I can say that this app is pretty horrendous
2 replies
CC#
Created by Denis on 7/20/2023 in #help
❔ Change active repositories in Visual Studio - Multiple repositories
3 replies
CC#
Created by Denis on 7/18/2023 in #help
❔ WinFroms app with 10k+ handles and not crashing?
2 replies
CC#
Created by Denis on 6/30/2023 in #help
✅ FileSystemWatcher - avoiding buffer overflow
I have a file-based communication protocol - insane, I know. It is used to communicate many messages a second. The FileSystemWatcher has a default buffer size of 8KB, which allows up to 15 notifications, assuming the filenames are 260 characters long. Let's say I increase the buffer size 2x. Is there anything else I can do to avoid the notification buffer from overflowing? I have also configured the following:
_watcher = new FileSystemWatcher
{
Path = _path,
InternalBufferSize = 16_384, // 16KB
IncludeSubdirectories = false,
Filter = "*_*_*_*_*" + Constants.FILE_EXTENSION,
NotifyFilter = NotifyFilters.CreationTime
};
_watcher.Created += OnCreated;
_watcher = new FileSystemWatcher
{
Path = _path,
InternalBufferSize = 16_384, // 16KB
IncludeSubdirectories = false,
Filter = "*_*_*_*_*" + Constants.FILE_EXTENSION,
NotifyFilter = NotifyFilters.CreationTime
};
_watcher.Created += OnCreated;
I'm also thinking about invoking the body of the OnCreated handler in a background thread. Is that a bad idea?
private void OnCreated(object source, FileSystemEventArgs e)
{
ThreadPool.QueueUserWorkItem(_ =>
{
var fullName = Path.GetFileNameWithoutExtension(e.FullPath);
var isMatch = Regex.IsMatch(fullName, pattern);

if (!isMatch)
return;

if (DisableFileQueue)
NewMessage?.Invoke(this, e.FullPath);
else
FileQueue.Add(e.FullPath);
});
}
private void OnCreated(object source, FileSystemEventArgs e)
{
ThreadPool.QueueUserWorkItem(_ =>
{
var fullName = Path.GetFileNameWithoutExtension(e.FullPath);
var isMatch = Regex.IsMatch(fullName, pattern);

if (!isMatch)
return;

if (DisableFileQueue)
NewMessage?.Invoke(this, e.FullPath);
else
FileQueue.Add(e.FullPath);
});
}
14 replies
CC#
Created by Denis on 6/23/2023 in #help
❔ .NET Service discovery
I have a gRPC server installed on a workstation in an on-premise local network. And I have client applications that are installed on one or more machines in the same network. I wish the client applications could search the network and automatically find the gRPC server. Afaik, this was/is possible with WCF Discovery. How should this be done for gRPC? Is there a library I could use? Assume that the server and client apps are installed in a non-enterprise environment - customers have bare-bones IT infrastructures.
11 replies