Method Event

Summary

Define Post and Undo post on the Order entity. Add the code that runs them in a Logic class. Core creates API endpoints. Client buttons use the server's labels and rules.

The examples use the C# type DocsOrder, displayed as Order. See the complete Order example for all models, logic, and client code.

Defining Methods

[Method("Post", MethodType.Modify, SaveOnCompletion = true)]
public partial void Post();

[Method("Undo post", MethodType.Modify, SaveOnCompletion = true)]
public partial void UndoPost();

Add these methods to the partial entity class. SaveOnCompletion saves after all handlers finish.

MethodType

Type HTTP Use
Read GET Return data without changing stored records.
Modify POST Change data.

Parameter rules

Non-nullable parameters without defaults are required. Nullable parameters and parameters with defaults are optional. Missing required parameters return 400 BadRequest.

Implementing Methods

Instance method

Register the Order handlers in its Logic class:

[Logic]
public class OrderBL(DocsOrder.Logic order)
{
    [RegisterLogic]
    public void Posting()
    {
        order.Method(o => o.Post)
            .DisableIf(o => o.IsPosted)
            .Do(o => o.IsPosted = true)
            .OnSuccess("Order posted.");

        order.Method(o => o.UndoPost)
            .DisableIf(o => !o.IsPosted)
            .Do(o => o.IsPosted = false);
    }
}

Users can run Post on drafts and UndoPost on posted Orders. The API does not allow direct edits to IsPosted. Clients must use these methods. The complete example also makes Description and Amount read-only after posting.

Success messages

Put OnSuccess after Do. Core sends the message after the handlers and any SaveOnCompletion save succeed. The client button displays it automatically.

Order posted message

To include the Order number, use a message function:

order.Method(o => o.Post)
    .Do(o => o.IsPosted = true)
    .OnSuccess(o => $"Order {o.DocNumber} posted.");

Use this instead of the fixed message above.

When success messages appear

Disabled, aborted, or failed methods and failed saves show no success message. Validation questions delay it. Accepting shows it; rejecting cancels it. The method does not run again.

OnSuccess supports instance Read and Modify methods, including void methods. Static methods are not supported. Core does not add a default success message. Inside a workflow, a method may succeed before the workflow is saved.

When does it fire?

A method runs when called from code or through its API endpoint. The client can run it with <MethodButton Method="Post" /> inside a DataGraph. See Method Buttons and Split Buttons.

Scenarios

Use these patterns as needed.

1. Method that changes data (POST)

The Order's Post is a Modify method. Call it on a saved Order:

POST /api/DocsOrder({orderId})/Post

Pass Modify parameters in a JSON body. For example, public partial void SetNote(string note) receives:

POST /api/DocsOrder({orderId})/SetNote
Content-Type: application/json

{ "note": "Deliver before noon" }

Its handler reads the typed parameter as args.note. SetNote illustrates parameter syntax; the demo uses a Change note dialog.

2. Method that only reads data (GET)

A Read method can calculate a value. Add this optional method to DocsOrder:

[Method("Amount with tax", MethodType.Read)]
public partial decimal AmountWithTax(decimal taxRate);

Register it in a [RegisterLogic] method:

order.Method(o => o.AmountWithTax).Do((o, args) => o.Amount * (1 + args.taxRate));
GET /api/DocsOrder({orderId})/AmountWithTax()?taxRate=0.08

3. Static method to query open orders (GET)

A static method runs without a current record:

[Method("Open orders", MethodType.Read)]
public static partial IQueryable<DocsOrder> GetOpenOrders();

Static method

Register a static method without an entity parameter:

order.Method(_ => DocsOrder.GetOpenOrders)
    .Do(args => args.GetEntities<DocsOrder>().Where(o => !o.IsPosted));

Entity collections support normal OData options such as $select, $expand, and $filter:

GET /api/DocsOrder/GetOpenOrders()?$select=Guid,DocNumber,Description,Amount

4. Parameterless method

Read routes require (), even with no inputs. Modify routes do not need parentheses. The demo's ChangeNote Read method returns a virtual dialog entity:

GET /api/DocsOrder({orderId})/ChangeNote()

See Workflow Dialogs for its declaration and handler.

5. Computing a property when a method executes

For a stored IsPosted property without a setter, compute its value from the method that marked it dirty:

[ApiEntity]
public partial class Journal
{
    [Property<DataTypes.Boolean>("Is posted")]
    public partial bool IsPosted { get; }

    [Method("Post", MethodType.Modify)]
    public partial void Post();

    [Method("Undo post", MethodType.Modify)]
    public partial void UndoPost();
}
[Logic]
public class JournalBL(Journal.Logic journal)
{
    [RegisterLogic]
    public void Posting()
    {
        journal.Compute(j => j.IsPosted)
            .From((j, args) => args.PropertyState.DirtiedBy switch
            {
                nameof(Journal.Post) => true,
                nameof(Journal.UndoPost) => false,
                _ => args.PropertyState.ValidValue,
            })
            .DirtyByMethod(j => j.Post)
            .DirtyByMethod(j => j.UndoPost);

        journal.Method(j => j.Post).Do(_ => { });
        journal.Method(j => j.UndoPost).Do(_ => { });
    }
}

Keep ValidValue when neither method caused the update. Empty handlers make the methods available; Compute changes the status. See Dirty from a method for timing and notifications.

6. Disabling a method with DisableIf (Disabled UI button)

order.Method(o => o.Post)
    .DisableIf(o => o.IsPosted)
    .Do(o => o.IsPosted = true);

Use DisableIf to prevent users from clicking the method. Any active true condition disables the method. Keep conditions fast; Core checks them on each client request.

Disabled names appear in RecordReadOnly. A MethodButton stays visible but disabled; a SplitButton hides the unavailable action. See Read-only.

7. Aborting a method with AbortIf

Use AbortIf to stop a method and explain why when it is called. For example, replace DisableIf with:

order.Method(o => o.Post)
    .AbortIf(o => o.IsPosted)
    .WithMessage(o => $"Order {o.DocNumber} is already posted.")
    .Do(o => o.IsPosted = true);

Asking the user instead of aborting

Use AskUser to confirm before running the handler, such as posting an archived Order:

order.Method(o => o.Post)
    .AbortIf(o => o.IsArchived)
    .AskUser("Post this archived order?", acceptButton: "Post", rejectButton: "Cancel")
    .Do(o => o.IsPosted = true);

Inside a workflow, Core keeps the arguments and waits for acceptance. Outside a workflow, it uses the default answer. See Questions.

8. Aborting during method execution

AbortIf and DisableIf run before the handlers. If a handler changes data, use UndoOnAbort to say how Core should undo that change:

order.Method(o => o.Post).Do((o, args) =>
{
    var previousStatus = o.IsPosted;
    o.IsPosted = true;
    args.UndoOnAbort(record => record.IsPosted = previousStatus);
});

A later handler can call args.AbortWithMessage(...) to stop the method. Core skips the remaining handlers and runs undo handlers in reverse order. Changes without UndoOnAbort stay pending. Put each undo beside the change it reverses.

9. Save on completion

Set SaveOnCompletion = true on [Method] to save after the handlers finish. Do not also call args.Context.SaveChanges() in a handler. Core checks errors and required properties, runs the enabled handlers, then saves once. If a handler or save fails, Core runs the registered undo handlers.

10. Subscribers and features

Each handler is a subscriber. It and its rules run only when the tenant has that feature enabled. The handler can read that feature's properties and entities. The last enabled handler supplies the return value.

If all handlers belong to unavailable features, Core reports that the method is unavailable. If no handlers are registered, Core reports a configuration error.

Controlling execution

  • DisableIf disables the button and prevents the method from running.
  • AbortIf stops the method with an explanation.
  • UndoOnAbort undoes registered changes if the method aborts or its final save fails.

OData API Routes

Method type HTTP Route
Instance Read GET /api/{Entity}({id})/MethodName()
Instance Modify POST /api/{Entity}({id})/MethodName
Static Read GET /api/{Entity}/MethodName()
Static Modify POST /api/{Entity}/MethodName

GET parameters use the query string. POST parameters use a JSON body.

Notes

  • args exposes typed parameters, GetService<T>(), and GetEntities<T>().
  • Read methods should not change stored records.
  • Core generates API endpoints from method declarations.
  • For application notifications after instance methods finish, see Method listeners.