Table of Contents

Creating a server method that reads data

In this article, you will learn how to create a server method, exposed as an API, which given a quantity indicates whether a product can be ordered.

If Discontinued is not checked on the product, the method must always return true.

If Discontinued is checked on the product, the method should return true only if sufficient stock is available.

Prerequisite

This article assumes that you have followed the previous quick start guide chapters and that you are familiar with Visual Studio and xUnit.

Before starting, you must add a new required boolean property named Discontinued with false as default value on the Product entity and restart the metadata generation process from this entity.

Creating the server method metadata and generating its implementation class

In Neos Studio, add a new server method with the following properties:

Method name = CanQuantityBeOrdered
Description = Indicates whether a quantity can be ordered for a given product
Expose as API = true

It must be asynchronous, take two parameters and return a boolean :

public async Task<bool> ExecuteAsync(int productId, int quantity)

Save the changes. Press the Visual Studio button on the CanQuantityBeOrdered server method screen in Neos Studio.

The save creates the following files (the corresponding class with a unit test class):

Catalog
└───businessAssembly
    └───Application
    │   └───Methods
    │       │   CanQuantityBeOrdered.cs
    └───Application.Tests.UnitTests
        └───Methods
            │   CanQuantityBeOrderedTests.cs

Review of the generated code

CanQuantityBeOrdered.cs

This file contains the implementation of the server method. The default implementation is just an example and must be modified.

/// <summary>
/// Represents CanQuantityBeOrdered method.
/// </summary>
public class CanQuantityBeOrdered : ICanQuantityBeOrdered
{
    private readonly INeosLogger<ICanQuantityBeOrdered> _logger;

    /// <summary>
    /// Initializes a new instance of the <see cref="CanQuantityBeOrdered"/> class.
    /// </summary>
    /// <param name="logger">Logger.</param>
    public CanQuantityBeOrdered(INeosLogger<ICanQuantityBeOrdered> logger)
    {
        _logger = logger;
    }

    ///<inheritdoc/>
    public async Task<bool> ExecuteAsync(int productId, int quantity)
    {
        throw new GroupeIsa.Neos.Domain.Exceptions.BusinessException("Error");
    }
}

CanQuantityBeOrderedTests.cs

This file contains the unit tests of the server method. The default implementation is just an example and must be modified.

    [Fact]
    public async Task ExecuteAsyncShouldWork()
    {
        // Arrange
        ICanQuantityBeOrdered method = Mocker.CreateInstance<CanQuantityBeOrdered>();

        // Act
        System.Action action = () => method.ExecuteAsync();

        // Assert
        action.Should().NotThrow();
    }

Server method implementation

You will follow a TDD process to implement the validation rule.

Not discontinued product use case

Rename the test method and modify its implementation:

public class CanQuantityBeOrderedTests
{
    [Fact]
    public async Task ExecuteAsyncWithNotDiscontinuedProductShouldReturnTrue()
    {
        // Arrange
        int productId = 1;
        ICanQuantityBeOrdered method = Mocker.CreateInstance<CanQuantityBeOrdered>();

        // Act
        bool result = await method.ExecuteAsync(productId, 1);

        // Assert
        result.Should().BeTrue();
    }
}

Run the test method, it will throw an exception.

Modify the server method:

/// <summary>
/// Represents CanQuantityBeOrdered server method implementation.
/// </summary>
public class CanQuantityBeOrdered : ICanQuantityBeOrdered
{
    private readonly INeosLogger<ICanQuantityBeOrdered> _logger;
    private readonly IProductViewRepository _productViewRepository;

    /// <summary>
    /// Initializes a new instance of the <see cref="CanQuantityBeOrdered"/> class.
    /// </summary>
    /// <param name="logger">Logger.</param>
    /// <param name="productViewRepository">ProductViews repository.</param>
    public CanQuantityBeOrdered(INeosLogger<ICanQuantityBeOrdered> logger, IProductViewRepository productViewRepository)
    {
        _logger = logger;
        _productViewRepository = productViewRepository;
    }

    ///<inheritdoc/>
    public async Task<bool> ExecuteAsync(int productId, int quantity)
    {
        IProductView? productView = await _productViewRepository.FindAsync(productId);
        if (productView == null)
        {
            return false;
        }

        return !productView.Discontinued;
    }
}

You can notice the use of a new dependency injected in the constructor: the product views repository.

If you run the unit test, it will fail. You must mock IProductViewRepository to obtain a product that is not discontinued when the test is runned.

Replace the test Arrange section as follows:

// Arrange
int productId = 1;
ICanQuantityBeOrdered method = Mocker.CreateInstance<CanQuantityBeOrdered>();
IProductView productView = GetEntityViewRepository<IProductView>().AddNew();
SetAutoincrementedKeyValue(productView, productId);
productView.Discontinued = true;
productView.UnitsInStock = unitsInStock;
productView.UnitsOnOrder = unitsOnOrder;
AcceptEntityViewChanges();

// Act
bool result = await method.ExecuteAsync(productId, quantityToOrder);

// Assert
result.Should().Be(expectedResult);

This code creates a mock for IProductView with Discontinued = false.

Run the unit test, it succeeds.

Discontinued Product use case

Add a new test method in CanQuantityBeOrderedTests.cs:

[Theory]
[InlineData(10, 5, 4, true)]
[InlineData(10, 6, 5, false)]
public async Task ExecuteAsyncWithDiscontinuedProductShouldWork(int unitsInStock, int unitsOnOrder, int quantityToOrder, bool expectedResult)
{
    // Arrange
    int productId = 1;
    ICanQuantityBeOrdered method = Mocker.CreateInstance<CanQuantityBeOrdered>();
    IProductView productView = GetEntityViewRepository<IProductView>().AddNew();
    SetAutoincrementedKeyValue(productView, productId);
    productView.Discontinued = true;
    productView.UnitsInStock = unitsInStock;
    productView.UnitsOnOrder = unitsOnOrder;
    AcceptEntityViewChanges();

    // Act
    bool result = await method.ExecuteAsync(productId, quantityToOrder);

    // Assert
    result.Should().Be(expectedResult);
}

This unit test method represents two use cases:

  • I can order a discontinued product if the available stock (UnitsInStock - UnitsOnOrder) is greater than or equal to the quantity to order.
  • I can't order a discontinued product if the available stock (UnitsInStock - UnitsOnOrder) is lesser than the quantity to order.

Update your server method to make these tests pass:

///<inheritdoc/>
public async Task<bool> ExecuteAsync(int productId, int quantity)
{
    IProductView? productView = await _productViewRepository.FindAsync(productId);
    if (productView == null)
    {
        return false;
    }

    if (productView.Discontinued)
    {
        return quantity <= productView.UnitsInStock - productView.UnitsOnOrder;
    }
    else
    {
        return true;
    }
}

Calling the server method

You will now see how to call your server method.

From swagger

Because we checked Expose as API in the properties of the server method, the generated application automatically exposes it.

Run the generated application server with the following command in the cluster folder:

neos run

Open https://localhost:6005/index.html in your web browser:

Your server method appears in the list of available APIs and you can try it.