Skip to content

Added .NET SDK examples to serialization document #4596

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: v1.15
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
96a1e27
Added .NET SDK examples to serialization document + modernized it some
WhitWaldo Mar 22, 2025
834354e
Merge branch 'v1.15' into serialization-update
WhitWaldo Mar 28, 2025
d062507
Update sdk-serialization.md
msfussell Apr 12, 2025
7f10b3a
Merge branch 'v1.15' into serialization-update
msfussell Apr 12, 2025
64ebea9
Merge branch 'v1.15' into serialization-update
WhitWaldo Apr 13, 2025
6feb78d
Merge branch 'v1.15' into serialization-update
msfussell Apr 13, 2025
ec16e60
Merge branch 'v1.15' into serialization-update
msfussell Apr 15, 2025
4ab5a6f
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 18, 2025
e16009a
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 18, 2025
e105212
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 18, 2025
a5384a1
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 18, 2025
31ca816
Updated serialization package links + added reference to actor serial…
WhitWaldo Apr 18, 2025
4029fb9
Added input binding examples for .NET
WhitWaldo Apr 18, 2025
daf4248
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 19, 2025
8db9133
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 19, 2025
f6c7ffa
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 19, 2025
3c6fa22
Update daprdocs/content/en/developing-applications/local-development/…
WhitWaldo Apr 19, 2025
e333177
Update daprdocs/content/en/developing-applications/local-development/…
msfussell Apr 21, 2025
e641114
Merge branch 'v1.15' into serialization-update
WhitWaldo Apr 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,40 @@ aliases:
- '/developing-applications/sdks/serialization/'
---

An SDK for Dapr should provide serialization for two use cases. First, for API objects sent through request and response payloads. Second, for objects to be persisted. For both these use cases, a default serialization is provided. In the Java SDK, it is the [DefaultObjectSerializer](https://dapr.github.io/java-sdk/io/dapr/serializer/DefaultObjectSerializer.html) class, providing JSON serialization.
Dapr SDKs provide serialization for two use cases. First, for API objects sent through request and response payloads. Second, for objects to be persisted. For both of these cases, a default serialization method is provided in each language SDK.

| Language SDK | Default Serializer |
|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [.NET]({{< ref dotnet >}}) | [DataContracts](https://learn.microsoft.com/dotnet/framework/wcf/feature-details/using-data-contracts) for remoted actors, [System.Text.Json](https://www.nuget.org/packages/System.Text.Json) otherwise. Read more about .NET serialization [here]({{< ref dotnet-actors-serialization >}}) | |
| [Java]({{< ref java >}}) | [DefaultObjectSerializer](https://dapr.github.io/java-sdk/io/dapr/serializer/DefaultObjectSerializer.html) for JSON serialization |
| [JavaScript]({{< ref js >}}) | JSON |

## Service invocation

{{< tabs ".NET" "Java" >}}

<!-- .NET -->
{{% codetab %}}

```csharp
using var client = (new DaprClientBuilder()).Build();
await client.InvokeMethodAsync("myappid", "saySomething", "My Message");
```

{{% /codetab %}}

<!-- Java -->
{{% codetab %}}

```java
DaprClient client = (new DaprClientBuilder()).build();
client.invokeService("myappid", "saySomething", "My Message", HttpExtension.POST).block();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
client.invokeService("myappid", "saySomething", "My Message", HttpExtension.POST).block();
client.invokeMethod("myappid", "saySomething", "My Message", HttpExtension.POST).block();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the newer java examples we are using invokeMethod not invokeService.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cicoyle can you confirm that this is the correct example we should be giving in the docs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't even look at the other languages as I assumed they were up to speed.

```

In the example above, the app will receive a `POST` request for the `saySomething` method with the request payload as `"My Message"` - quoted since the serializer will serialize the input String to JSON.
{{% /codetab %}}

In the example above, the app `myappid` receives a `POST` request for the `saySomething` method with the request payload as
`"My Message"` - quoted since the serializer will serialize the input String to JSON.

```text
POST /saySomething HTTP/1.1
Expand All @@ -30,11 +54,35 @@ Content-Length: 12

## State management

{{< tabs ".NET" "Java" >}}

<!-- .NET -->
{{% codetab %}}

```csharp
using var client = (new DaprClientBuilder()).Build();
var state = new Dictionary<string, string>
{
{ "key": "MyKey" },
{ "value": "My Message" }
};
await client.SaveStateAsync("MyStateStore", "MyKey", state);
```

{{% /codetab %}}

<!-- Java -->
{{% codetab %}}

```java
DaprClient client = (new DaprClientBuilder()).build();
client.saveState("MyStateStore", "MyKey", "My Message").block();
```
In this example, `My Message` will be saved. It is not quoted because Dapr's API will internally parse the JSON request object before saving it.

{{% /codetab %}}

In this example, `My Message` is saved. It is not quoted because Dapr's API internally parse the JSON request
object before saving it.

```JSON
[
Expand All @@ -47,12 +95,45 @@ In this example, `My Message` will be saved. It is not quoted because Dapr's API

## PubSub

{{< tabs ".NET" "Java" >}}

<!-- .NET -->
{{% codetab %}}

```csharp
using var client = (new DaprClientBuilder()).Build();
await client.PublishEventAsync("MyPubSubName", "TopicName", "My Message");
```

The event is published and the content is serialized to `byte[]` and sent to Dapr sidecar. The subscriber receives it as a [CloudEvent](https://github.com/cloudevents/spec). Cloud event defines `data` as String. The Dapr SDK also provides a built-in deserializer for `CloudEvent` object.

```csharp
public async Task<IActionResult> HandleMessage(string message)
{
//ASP.NET Core automatically deserializes the UTF-8 encoded bytes to a string
return new Ok();
}
```

or

```csharp
app.MapPost("/TopicName", [Topic("MyPubSubName", "TopicName")] (string message) => {
return Results.Ok();
}
```

{{% /codetab %}}

<!-- Java -->
{{% codetab %}}

```java
DaprClient client = (new DaprClientBuilder()).build();
client.publishEvent("TopicName", "My Message").block();
```

The event is published and the content is serialized to `byte[]` and sent to Dapr sidecar. The subscriber will receive it as a [CloudEvent](https://github.com/cloudevents/spec). Cloud event defines `data` as String. Dapr SDK also provides a built-in deserializer for `CloudEvent` object.
The event is published and the content is serialized to `byte[]` and sent to Dapr sidecar. The subscriber receives it as a [CloudEvent](https://github.com/cloudevents/spec). Cloud event defines `data` as String. The Dapr SDK also provides a built-in deserializer for `CloudEvent` objects.

```java
@PostMapping(path = "/TopicName")
Expand All @@ -62,9 +143,50 @@ The event is published and the content is serialized to `byte[]` and sent to Dap
}
```

{{% /codetab %}}

## Bindings

In this case, the object is serialized to `byte[]` as well and the input binding receives the raw `byte[]` as-is and deserializes it to the expected object type.
For output bindings the object is serialized to `byte[]` whereas the input binding receives the raw `byte[]` as-is and deserializes it to the expected object type.

{{< tabs ".NET" "Java" >}}

<!-- .NET -->
{{% codetab %}}

* Output binding:
```csharp
using var client = (new DaprClientBuilder()).Build();
await client.InvokeBindingAsync("sample", "My Message");
```

* Input binding (controllers):
```csharp
[ApiController]
public class SampleController : ControllerBase
{
[HttpPost("propagate")]
public ActionResult<string> GetValue([FromBody] int itemId)
{
Console.WriteLine($"Received message: {itemId}");
return $"itemID:{itemId}";
}
}
```

* Input binding (minimal API):
```csharp
app.MapPost("value", ([FromBody] int itemId) =>
{
Console.WriteLine($"Received message: {itemId}");
return ${itemID:{itemId}";
});
* ```

{{% /codetab %}}

<!-- Java -->
{{% codetab %}}

* Output binding:
```java
Expand All @@ -80,15 +202,49 @@ In this case, the object is serialized to `byte[]` as well and the input binding
System.out.println(message);
}
```

{{% /codetab %}}

It should print:
```
My Message
```

## Actor Method invocation
Object serialization and deserialization for invocation of Actor's methods are same as for the service method invocation, the only difference is that the application does not need to deserialize the request or serialize the response since it is all done transparently by the SDK.
Object serialization and deserialization for Actor method invocation are same as for the service method invocation,
the only difference is that the application does not need to deserialize the request or serialize the response since it
is all done transparently by the SDK.

For Actor methods, the SDK only supports methods with zero or one parameter.

{{< tabs ".NET" "Java" >}}

For Actor's methods, the SDK only supports methods with zero or one parameter.
The .NET SDK supports two different serialization types based on whether you're using strongly-typed (DataContracts)
or weakly-typed (DataContracts or System.Text.JSON) actor client. [This document]({{< ref dotnet-actors-serialization >}})
can provide more information about the differences between each and additional considerations to keep in mind.

<!-- .NET -->
{{% codetab %}}

* Invoking an Actor's method using the weakly-typed client and System.Text.JSON:
```csharp
var proxy = this.ProxyFactory.Create(ActorId.CreateRandom(), "DemoActor");
await proxy.SayAsync("My message");
```

* Implementing an Actor's method:
```csharp
public Task SayAsync(string message)
{
Console.WriteLine(message);
return Task.CompletedTask;
}
```

{{% /codetab %}}

<!-- Java -->
{{% codetab %}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't think that this invokeActorMethod is correct anymore (unless you're using springboot?). @cicoyle can you take a peek at this actors example?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call out - If Whit has the bandwidth and would like to update the Java code samples that would be great, but given this PR is for .NET, Im fine with proceeding with this PR and opening follow up issues to ensure the java sdk docs are up to date. I think that would be the best path forward to ensure proper prioritization of the work and not halt this PR that is updating .Net. We should definitely update the Java SDK documentation to align more clearly with our working examples in DemoActorClient.java and ActorTurnBasedConcurrencyIT.java.


* Invoking an Actor's method:
```java
Expand All @@ -105,13 +261,37 @@ public String say(String something) {
return "OK";
}
```

{{% /codetab %}}

It should print:
```
My Message
```

## Actor's state management
Actors can also have state. In this case, the state manager will serialize and deserialize the objects using the state serializer and handle it transparently to the application.
Actors can also have state. In this case, the state manager will serialize and deserialize the objects using the state
serializer and handle it transparently to the application.

<!-- .NET -->
{{% codetab %}}

```csharp
public Task SayAsync(string message)
{
// Reads state from a key
var previousMessage = await this.StateManager.GetStateAsync<string>("lastmessage");

// Sets the new state for the key after serializing it
await this.StateManager.SetStateAsync("lastmessage", message);
return previousMessage;
}
```

{{% /codetab %}}

<!-- Java -->
{{% codetab %}}

```java
public String actorMethod(String message) {
Expand All @@ -124,12 +304,17 @@ public String actorMethod(String message) {
}
```

{{% /codetab %}}

## Default serializer

The default serializer for Dapr is a JSON serializer with the following expectations:

1. Use of basic [JSON data types](https://www.w3schools.com/js/js_json_datatypes.asp) for cross-language and cross-platform compatibility: string, number, array, boolean, null and another JSON object. Every complex property type in application's serializable objects (DateTime, for example), should be represented as one of the JSON's basic types.
2. Data persisted with the default serializer should be saved as JSON objects too, without extra quotes or encoding. The example below shows how a string and a JSON object would look like in a Redis store.
1. Use of basic [JSON data types](https://www.w3schools.com/js/js_json_datatypes.asp) for cross-language and cross-platform compatibility: string, number, array,
boolean, null and another JSON object. Every complex property type in application's serializable objects (DateTime,
for example), should be represented as one of the JSON's basic types.
2. Data persisted with the default serializer should be saved as JSON objects too, without extra quotes or encoding.
The example below shows how a string and a JSON object would look like in a Redis store.
```bash
redis-cli MGET "ActorStateIT_StatefulActorService||StatefulActorTest||1581130928192||message
"This is a message to be saved and retrieved."
Expand All @@ -140,7 +325,8 @@ redis-cli MGET "ActorStateIT_StatefulActorService||StatefulActorTest||1581130928
```
3. Custom serializers must serialize object to `byte[]`.
4. Custom serializers must deserialize `byte[]` to object.
5. When user provides a custom serializer, it should be transferred or persisted as `byte[]`. When persisting, also encode as Base64 string. This is done natively by most JSON libraries.
5. When user provides a custom serializer, it should be transferred or persisted as `byte[]`. When persisting, also
encode as Base64 string. This is done natively by most JSON libraries.
```bash
redis-cli MGET "ActorStateIT_StatefulActorService||StatefulActorTest||1581130928192||message
"VGhpcyBpcyBhIG1lc3NhZ2UgdG8gYmUgc2F2ZWQgYW5kIHJldHJpZXZlZC4="
Expand All @@ -149,5 +335,3 @@ redis-cli MGET "ActorStateIT_StatefulActorService||StatefulActorTest||1581130928
redis-cli MGET "ActorStateIT_StatefulActorService||StatefulActorTest||1581130928192||mydata
"eyJ2YWx1ZSI6Ik15IGRhdGEgdmFsdWUuIn0="
```

*As of now, the [Java SDK](https://github.com/dapr/java-sdk/) is the only Dapr SDK that implements this specification. In the near future, other SDKs will also implement the same.*
Loading