Dialogs and notifications
A dialog is declared by the view and rendered closed by the shell; a controller or an interaction opens and closes it by key. Notifications are a client effect with nowhere of their own on the tree — they stack up in a corner the view chooses.
Declaring a dialog
Override CreateDialogs() on the view and return a list of UIDialog:
protected override IReadOnlyList<UIDialog> CreateDialogs()
=> [
new UIDialog
{
Key = DialogTestController.ConfirmKey,
CloseOnBackdrop = false,
CloseOnEscape = false,
Content = CreatePanel("Delete this build?", ...,
CreateButtons(
CreateButton("Cancel", nameof(DialogTestController.KeepBuild), UIButtonType.Ghost),
CreateButton("Delete", nameof(DialogTestController.DeleteBuild), UIButtonType.Danger)
)
)
}
];
UIDialog:
| Member | Meaning |
|---|---|
Key |
stable string a command or interaction opens/closes it by |
Content |
the root component of the dialog's body |
Surface (UISurfaceStyle) |
what the panel is made of — Background, Raised (default), Tinted |
Placement (UIDialogPlacement) |
Center (default), or a full-length sheet against Left, Right, Top, Bottom |
Modal |
whether it blocks interaction with the view underneath (default true) |
CloseOnBackdrop |
whether clicking the backdrop closes it (default true) |
CloseOnEscape |
whether Escape closes it (default true) |
A destructive confirmation turns both CloseOnBackdrop and CloseOnEscape off, so only its own buttons answer
it. A non-modal sheet (Modal = false) at an edge, drawn on Surface = Background, is the shape a filter
drawer or a side panel takes — the page keeps working beside it.
Opening and closing
From a controller, Context.Dialogs (IUIDialogService):
_ = await Context.Dialogs.ShowAsync(Context.Handle, ProgressKey, cancellationToken).ConfigureAwait(false);
// ... work ...
_ = await Context.Dialogs.HideAsync(Context.Handle, ProgressKey, cancellationToken).ConfigureAwait(false);
ShowAsync/HideAsync push straight to the connection, so the dialog stands open for exactly as long as the
command takes — the pattern for a wait dialog nothing on its own body dismisses.
A command can instead return the effect, which is the more common shape when opening or closing is the whole of what the command does:
[UICommand]
public UICommandResult OpenFilters()
=> UICommandResult.Ok([new OpenDialogEffect(FiltersKey)]);
[UICommand]
public UICommandResult SaveEdit()
{
// ... write the draft back ...
return UICommandResult.Ok([new CloseDialogEffect(EditKey), new ShowNotificationEffect($"Saved '{Name}'.", UIColorStyle.Success)]);
}
OpenDialogEffect(dialogKey) and CloseDialogEffect(dialogKey) both take just the key. An interaction can
raise the same effects directly, with no round trip to the controller at all — see the interactions guide.
Notifications
ShowNotificationEffect(string message, UIColorStyle severity = UIColorStyle.Info) raises a toast:
return UICommandResult.Ok([new ShowNotificationEffect("Build #481 published.", UIColorStyle.Success)]);
Severity is a UIColorStyle — Info, Success, Warning, Danger are the ones the demo uses. Where a view's
notifications stack is UIViewOptions.NotificationPlacement (UINotificationPlacement: Bottom — the default
— or Top), set once for the view rather than per notification.
When a command fails on its own
The client never reads a raw success/failure flag — both the invoke return and the push channel only apply
changes and effects. So a command that throws, or is refused by an authorization check, and returns no effects
of its own gets one attached automatically: a ShowNotificationEffect built from UIErrorHandlingOptions,
configured through ConfigureErrorHandling:
builder.ConfigureErrorHandling(errors =>
{
errors.NotifyOnCommandFailure = true;
errors.IncludeExceptionDetail = false;
errors.CommandRefusedMessage = "You are not allowed to do that.";
errors.CommandFailedMessage = "Something went wrong. Please try again.";
errors.ErrorRoute = "/error";
errors.NotFoundRoute = "/not-found";
});
| Option | Meaning |
|---|---|
NotifyOnCommandFailure |
whether a failed command with no effects of its own is reported at all (default true) |
IncludeExceptionDetail |
whether the real exception message reaches the browser — off by default, since an exception routinely carries a connection string, a table name or a file path |
CommandRefusedMessage |
shown for an UnauthorizedAccessException |
CommandFailedMessage |
shown for any other exception, unless IncludeExceptionDetail is on |
ErrorRoute |
where an unhandled exception resolving a view goes; a failing command is reported in place, never routed here |
NotFoundRoute |
where a request for an unregistered route goes |
Returning your own effects from a failing command is how it takes over the reporting — the automatic notification only fires when a command's result carries none. All three messages go through the translator, so an application overrides them by localization rather than by re-implementing the mechanism.