State Management with Dapr.io

Prashant Odhavani
Simform Engineering
6 min readAug 14, 2024

Simplify state management using Dapr and efficiently handle user preferences, settings, or any other kind of external data.

What is Dapr?

Dapr stands for Distributed Application Runtime, a lightweight, open-source library designed to help manage application state. It encourages a centralized approach whereby the state is kept in a single location called a “store.” This store acts as the central hub for accessing, updating, and reacting to state changes throughout your application.

Dapr provides a consistent API across different state stores like Redis, Cosmos DB, and more. It decouples the application logic from the underlying storage technology. Abstraction of state management via Dapr makes things very easy for developers by keeping them focused on application development rather than certain database implementations.

Core concepts of Dapr

  • Stores: The core of Dapr, stores encapsulate the state of your application. You can create multiple stores to manage different parts of your application.
  • State: This refers to the data managed by a store, which can be any data structure you need.
  • Actions: Actions describe changes to the state. They take the current state and return a new one.
  • Selectors: Selectors are functions that extract specific data from the state, allowing you to access only the sub-parts of the state in your UI components.

Benefits of using Dapr

  • Improved maintainability: A centralized state makes an application easier to reason about and maintain.
  • Simplified code: Reactive updates reduce boilerplate code used for handling state changes in UI components.
  • Predictable behavior: Immutability ensures consistent application state and simplifies debugging.
  • Scalability: Centralized store architecture keeps larger applications scalable.
  • Flexibility: Middleware enables a developer to customize behavior based on specific needs.

Use Dapr state management with a .NET 8 Web API project

Prerequisites:

1. Project creation

Open your favorite terminal and run the following command to create a new ASP.NET Core Web API project:

dotnet new webapi -n DaprStateManagementBlogAPI

This command creates a new project named DaprStateManagementBlogAPI with the basic structure for a web API application.

2. Dapr configuration

Go to Project Directory:

cd DaprStateManagementBlogAPI

Install the required Dapr client library using NuGet:

dotnet add package Dapr.AspNetCore

Register Dapr client service in the Program.cs file:

builder.Services.AddDaprClient();

Configure State Management components.

You can make your configuration file anywhere you like and use it accordingly. Check out a good example of the Redis component YAML file below:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: blogPostStatestore
namespace: default
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: "localhost:6379" # Replace with your Redis host
- name: redisPassword
value: "your-redis-password" # Replace with your Redis password
- name: actorStateStore
value: "true"

3. Code examples of handling state — create, update, retrieve, delete, filter

We are going to use a blog service as an example to understand how to create, update, retrieve, delete, and filter Redis state store data. Here, we will focus on the main parts of the code block. You can get a working solution and learn about it on my GitHub Repository.

A. Store state

public async Task<string> CreateBlogPost(BlogPostDto blogPostDto)
{
// Create a new BlogPost object with a unique Id
var blogPost = new BlogPost
{
Id = Guid.NewGuid().ToString(),
Title = blogPostDto.Title,
Content = blogPostDto.Content,
Author = blogPostDto.Author,
Category = blogPostDto.Category,
CreatedAt = DateTime.UtcNow
};

// Save the BlogPost object to the state store asynchronously
await _daprClient.SaveStateAsync<BlogPost>(STORE_NAME, blogPost.Id, blogPost);

// Return the Id of the created BlogPost
return blogPost.Id;
}

B. Update state

public async Task<bool> UpdateBlogPost(string id, BlogPostDto blogPostDto)
{
// Retrieve the current BlogPost from the state store by its Id
var currentBlogPost = await _daprClient.GetStateAsync<BlogPost>(STORE_NAME, id);

// If the BlogPost exists, update its properties with the new values
if (currentBlogPost is not null)
{
currentBlogPost.Title = blogPostDto.Title;
currentBlogPost.Content = blogPostDto.Content;
currentBlogPost.Author = blogPostDto.Author;
currentBlogPost.Category = blogPostDto.Category;
currentBlogPost.UpdatedAt = DateTime.UtcNow;

// Save the updated BlogPost object back to the state store asynchronously
await _daprClient.SaveStateAsync<BlogPost>(STORE_NAME, currentBlogPost.Id, currentBlogPost);

// Return true indicating successful update
return true;
}

// Return false indicating the BlogPost with the given Id was not found
return false;
}

C. Get state

public async Task<BlogPost?> GetBlogPostById(string id)
{
// Retrieve a BlogPost from the state store by its Id asynchronously
var blogPost = await _daprClient.GetStateAsync<BlogPost>(STORE_NAME, id);

// Return the retrieved BlogPost or null if not found
return blogPost;
}

D. Delete state

public async Task<bool> DeleteBlogPost(string id)
{
// Delete a BlogPost from the state store by its Id asynchronously
await _daprClient.DeleteStateAsync(STORE_NAME, id);

// Return true indicating successful deletion
return true;
}

E. Filter state

public async Task<List<BlogPost>> GetBlogPostsByCategory(string category)
{
// Define a query to filter BlogPosts by category
var query = "{" +
"\"filter\": {" +
"\"EQ\": { \"category\": \"" + category + "\" }" +
"}}";

// Query the state store asynchronously based on the defined query
var queryResponse = await _daprClient.QueryStateAsync<BlogPost>(STORE_NAME, query);

// Extract BlogPost objects from the query response and order by CreatedAt descending
var blogPostList = queryResponse.Results.Select(q => q.Data).OrderByDescending(q => q.CreatedAt).ToList();

// Return the list of BlogPosts filtered by category
return blogPostList;
}

Now, let’s run the project and test our implementation.

Dapr command to run the project:

dapr run --app-id bloservice-api 
--app-port 7207
--dapr-http-port 3500
--app-protocol https
--resources-path D:\project\statemanagementdaprio\Components\
-- dotnet run --launch-profile https

You can find all the information regarding the dapr run CLI command here.

The above command, when run in the Package Manager console or terminal, will result in the above logs. These logs will inform you, “You’re app is up and running,” and give the listening URL. You can use that URL to call APIs.

Let’s grab the URL and open Swagger to test our APIs. (For my case, theURL is: https://localhost:7207/swagger/index.html). You can use any of your favorite tools. Here, I have used Swagger.

Call the POST API to save state data.

Call a GET API to obtain the state data.

Similarly, you can call the rest of the APIs. And in Redis, you can view the state data as shown in the image below:

The complete project is available on this GitHub repository.

Conclusion

State management using Dapr in a .NET 8 Web API project provides an abstracted and simpler way to handle application state. State interaction is thus decoupled from any specific storage technologies, such as SQL Server, Redis, or DynamoDB, which gives the application flexibility and scalability.

With Dapr, a developer can just focus on features and not look after the million complicated state interactions. This tutorial has demonstrated creating a .NET 8 Web API project using Dapr and configuring Redis state store, then applied CRUD operations to handle states. It gave you a view into how you may realize state management in your apps more maintainable, predictable, and efficient with Dapr. For more information, refer to the official Dapr documentation.

For more updates on the latest tools and technologies, follow the Simform Engineering blog.

Follow us: Twitter | LinkedIn

--

--