{"id":12024,"date":"2025-11-23T20:29:53","date_gmt":"2025-11-23T20:29:53","guid":{"rendered":"https:\/\/putridparrot.com\/blog\/?p=12024"},"modified":"2025-11-23T20:29:53","modified_gmt":"2025-11-23T20:29:53","slug":"calling-orleans-from-asp-net","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/calling-orleans-from-asp-net\/","title":{"rendered":"Calling Orleans from ASP.NET"},"content":{"rendered":"<p>In my last post <a href=\"https:\/\/putridparrot.com\/blog\/getting-started-with-orleans\/\" target=\"_blank\">Getting started with Orleans<\/a> we covered a lot of ground on the basics of setting up and using Orleans. It&#8217;s quite likely you&#8217;ll be wanting to use ASP.NET as an entry point to your Orleans code, so let&#8217;s look at how we might set this up.<\/p>\n<p>Create yourself an ASP.NET core project, I&#8217;m using controllers but minimal API is also fine (I just happened to have the option to use controllers selected).<\/p>\n<p>After you&#8217;ve created your application, clear out the weather forecast code etc. if you created the default sample. <\/p>\n<p>Add a folder for your grain(s) (mine&#8217;s named Grains, not very imaginative) and I&#8217;ve added the following files and code&#8230;<\/p>\n<p>IDocumentGrain.cs<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic interface IDocumentGrain : IGrainWithGuidKey\r\n{\r\n\r\n    Task&lt;string&gt; GetContent();\r\n    Task UpdateContent(string content);\r\n    Task&lt;DocumentMetadata&gt; GetMetadata();\r\n    Task Delete();\r\n}\r\n<\/pre>\n<p>DocumentGrain.cs<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class DocumentGrain(&#x5B;PersistentState(&quot;doc&quot;, &quot;documentStore&quot;)] IPersistentState&lt;DocumentState&gt; state)\r\n    : Grain, IDocumentGrain\r\n{\r\n    public Task&lt;string&gt; GetContent()\r\n    {\r\n        \/\/ State is hydrated automatically on activation\r\n        return Task.FromResult(state.State.Content);\r\n    }\r\n\r\n    public async Task UpdateContent(string content)\r\n    {\r\n        state.State.Content = content;\r\n        state.State.LastUpdated = DateTime.UtcNow;\r\n        await state.WriteStateAsync(); \/\/ persist changes\r\n    }\r\n\r\n    public Task&lt;DocumentMetadata&gt; GetMetadata()\r\n    {\r\n        var metadata = new DocumentMetadata\r\n        {\r\n            Title = state.State.Title,\r\n            LastUpdated = state.State.LastUpdated\r\n        };\r\n        return Task.FromResult(metadata);\r\n    }\r\n\r\n    public async Task Delete()\r\n    {\r\n        await state.ClearStateAsync(); \/\/ wipe persisted state\r\n    }\r\n}\r\n<\/pre>\n<p>DocumentMetadata.cs<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&#x5B;GenerateSerializer]\r\npublic class DocumentMetadata\r\n{\r\n    &#x5B;Id(0)]\r\n    public string Title { get; set; } = string.Empty;\r\n\r\n    &#x5B;Id(1)]\r\n    public DateTime LastUpdated { get; set; }\r\n}\r\n<\/pre>\n<p>DocumentState.cs<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic class DocumentState\r\n{\r\n    public string Title { get; set; } = string.Empty;\r\n    public string Content { get; set; } = string.Empty;\r\n    public DateTime LastUpdated { get; set; }\r\n}\r\n<\/pre>\n<p>Now we&#8217;ll add the DocumentController.cs in the Controllers folder <\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&#x5B;ApiController]\r\n&#x5B;Route(&quot;api\/&#x5B;controller]&quot;)]\r\npublic class DocumentController(IClusterClient client) : ControllerBase\r\n{\r\n    &#x5B;HttpGet(&quot;{id}&quot;)]\r\n    public async Task&lt;string&gt; Get(Guid id)\r\n    {\r\n        var grain = client.GetGrain&lt;IDocumentGrain&gt;(id);\r\n        return await grain.GetContent();\r\n    }\r\n}\r\n<\/pre>\n<p><em>Note: As we touched on in the previous post, we just use grains as if they already exist, the Orleans runtime will create and activate them if they do not exist or return them if the already exist.<\/em><\/p>\n<p>Finally in Program.cs add the following code after <em>builder.Services.AddControllers();<\/em><\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nbuilder.Host.UseOrleans(silo =&gt;\r\n{\r\n    silo.UseLocalhostClustering();\r\n    silo.AddMemoryGrainStorage(&quot;documentStore&quot;);\r\n    silo.UseDashboard(options =&gt;\r\n    {\r\n        options.HostSelf = true;\r\n        options.Port = 7000;\r\n    });\r\n});\r\n<\/pre>\n<p>When we run this application we will need to pass a GUID (as we&#8217;re using <em>IGrainWithGuidKey<\/em>) for example <em>https:\/\/localhost:7288\/api\/document\/B5D4A805-80C3-4239-967B-937A5A0E9250<\/em> and this will obviously send this request to the DocumentController Get endpoint and this uses a grain based upon the supplied id and calls the grain method <em>GetContent<\/em> which gets the current state <em>Content<\/em> property. <\/p>\n<p>I&#8217;ve not added code to call the other methods on the grain, but examples are listed for how these might look in the code above.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In my last post Getting started with Orleans we covered a lot of ground on the basics of setting up and using Orleans. It&#8217;s quite likely you&#8217;ll be wanting to use ASP.NET as an entry point to your Orleans code, so let&#8217;s look at how we might set this up. Create yourself an ASP.NET core [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[200,765],"tags":[],"class_list":["post-12024","post","type-post","status-publish","format-standard","hentry","category-asp-net-core","category-orleans"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/12024","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=12024"}],"version-history":[{"count":4,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/12024\/revisions"}],"predecessor-version":[{"id":12028,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/12024\/revisions\/12028"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=12024"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=12024"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=12024"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}