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.