1"use strict";(globalThis.webpackChunkdocs||=[]).push([[1403],{5613(e,n,t){t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>r,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>c});var a=t.cw((function(e,n){const a=t.p+"assets/images/contributors-7b1a49772ea2cbf64a2255715de6e052.png";t.d(n,["A",0,a])})),o=t(83075),s=t(74848),i=t(28453);const l={slug:"dotnet-sdk-v2",title:"OpenFeature .NET SDK 2.0 Release",date:new Date("2024-08-21T00:00:00.000Z"),authors:["toddbaert"],description:"Announcing the 2.0 Release of the OpenFeature SDK for .NET",tags:[".NET","dotnet","c#","csharp","async","v2.0","v2","sdk"],draft:!1},r=void 0,d={authorsImageUrls:[void 0]},c=[{value:"Notable Improvements",id:"notable-improvements",level:2},{value:"Support for Cancellation Tokens, Idiomatic Method Names",id:"support-for-cancellation-tokens-idiomatic-method-names",level:3},{value:"ValueTasks for Hooks",id:"valuetasks-for-hooks",level:3},{value:"Migration Steps",id:"migration-steps",level:2},{value:"Application Authors",id:"application-authors",level:3},{value:"Async suffixes and Cancellation Tokens",id:"async-suffixes-and-cancellation-tokens",level:4},{value:""Client Name" has Changed to "Domain"",id:"client-name-has-changed-to-domain",level:4},{value:"Provider Authors",id:"provider-authors",level:3},{value:"Provider Method Name Changes",id:"provider-method-name-changes",level:4},{value:"No Need for Provider Status",id:"no-need-for-provider-status",level:4},{value:"Hook Authors",id:"hook-authors",level:3},{value:"Hooks Must Return ValueTask",id:"hooks-must-return-valuetask",level:4},{value:"Full Changelog",id:"full-changelog",level:2},{value:"Thanks!",id:"thanks",level:2}];function h(e){const n={a:"a",code:"code",h2:"h2",h3:"h3",h4:"h4",p:"p",pre:"pre",...(0,i.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:"Today we're announcing the release of the OpenFeature SDK for .NET, v2.0!\nThis release contains several ergonomic improvements to the SDK, which .NET developers will appreciate.\nIt also includes some performance optimizations brought to you by the latest .NET primitives."}),"\n",(0,s.jsx)(n.h2,{id:"notable-improvements",children:"Notable Improvements"}),"\n",(0,s.jsx)(n.h3,{id:"support-for-cancellation-tokens-idiomatic-method-names",children:"Support for Cancellation Tokens, Idiomatic Method Names"}),"\n",(0,s.jsxs)(n.p,{children:["Methods on some key interfaces (such as provider resolvers) have been updated to indicate that they work asynchronously, by appending the ",(0,s.jsx)(n.code,{children:"Async"})," suffix in accordance with .NET conventions:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"// diff-remove\n await Api.Instance.SetProvider(myProviderInstance);\n// diff-add\n await Api.Instance.SetProviderAsync(myProviderInstance);\n"})}),"\n",(0,s.jsx)(n.p,{children:"Additionally, optional cancellation tokens can now be passed to applicable asynchronous methods.\nThis allows for the cancellation of async tasks, and is consistent with .NET norms:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:'// diff-remove\n await client.GetBooleanValue("my-flag", false, context, options);\n// diff-add\n await client.GetBooleanValueAsync("my-flag", false, context, options, cancellationToken);\n'})}),"\n",(0,s.jsx)(n.h3,{id:"valuetasks-for-hooks",children:"ValueTasks for Hooks"}),"\n",(0,s.jsxs)(n.p,{children:["The return types for stages within ",(0,s.jsx)(n.a,{href:"/docs/reference/concepts/hooks",children:"hooks"})," have been updated to take advantage of the performance benefit provided by ",(0,s.jsx)(n.a,{href:"https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/",children:"ValueTasks"}),".\nThe vast majority of hook stages run synchronously, and they are awaited internally by the SDK; so we can avoid the additional allocations associated with ",(0,s.jsx)(n.code,{children:"Tasks"})," by using ",(0,s.jsx)(n.code,{children:"ValueTasks"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"public class MyBeforeHook : Hook\n{\n// diff-remove-block-start\n public Task<EvaluationContext> Before<T>(HookContext<T> context,\n IReadOnlyDictionary<string, object> hints = null)\n {\n // code to run before flag evaluation\n }\n// diff-remove-block-end\n// diff-add-block-start\n public ValueTask<EvaluationContext> BeforeAsync<T>(HookContext<T> context,\n IReadOnlyDictionary<string, object> hints = null, CancellationToken cancellationToken = default)\n {\n // code to run before flag evaluation\n }\n// diff-add-block-end\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"migration-steps",children:"Migration Steps"}),"\n",(0,s.jsx)(n.h3,{id:"application-authors",children:"Application Authors"}),"\n",(0,s.jsx)(n.h4,{id:"async-suffixes-and-cancellation-tokens",children:"Async suffixes and Cancellation Tokens"}),"\n",(0,s.jsx)(n.p,{children:"Generally, application authors won't have to make many changes."}),"\n",(0,s.jsx)(n.p,{children:'As mentioned above, some async methods have been changed to conform to .NET conventions.\nEvaluation methods now have the "Async" suffix, and accept an optional cancellationToken:'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:'// diff-remove\n await client.GetBooleanValue("my-flag", false, context, options);\n// diff-add\n await client.GetBooleanValueAsync("my-flag", false, context, options, cancellationToken);\n'})}),"\n",(0,s.jsx)(n.p,{children:"The method for setting a provider has been updated similarly:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"// diff-remove\n await Api.Instance.SetProvider(myProviderInstance);\n// diff-add\n await Api.Instance.SetProviderAsync(myProviderInstance);\n"})}),"\n",(0,s.jsx)(n.h4,{id:"client-name-has-changed-to-domain",children:'"Client Name" has Changed to "Domain"'}),"\n",(0,s.jsxs)(n.p,{children:['Parameters previously named "client name" are now named "domain".\n"',(0,s.jsx)(n.a,{href:"https://openfeature.dev/specification/glossary/#domain",children:"Domains"}),'" are a more powerful concept than "named clients", but in instances where named clients were previously used, function identically.\nNo changes to consuming code are necessary these cases.']}),"\n",(0,s.jsx)(n.h3,{id:"provider-authors",children:"Provider Authors"}),"\n",(0,s.jsx)(n.h4,{id:"provider-method-name-changes",children:"Provider Method Name Changes"}),"\n",(0,s.jsx)(n.p,{children:"Provider authors must change their implementations in accordance with new methods names on the provider interface.\nOptionally, you can make use of the new cancellation token:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"// diff-remove-block-start\n\n public override Task<ResolutionDetail
1s<bool>> ResolveBooleanValue(\n string flagKey,\n bool defaultValue,\n EvaluationContext? context = null)\n {\n // return boolean flag details\n }\n// diff-remove-block-end\n// diff-add-block-start\n public override Task<ResolutionDetails<bool>> ResolveBooleanValueAsync(\n string flagKey,\n bool defaultValue,\n EvaluationContext? context = null,\n CancellationToken cancellationToken = default)\n {\n // return boolean flag details\n }\n// diff-add-block-end\n"})}),"\n",(0,s.jsx)(n.h4,{id:"no-need-for-provider-status",children:"No Need for Provider Status"}),"\n",(0,s.jsxs)(n.p,{children:["It's no longer necessary to define, update or expose provider status.\nIf your provider requires initialization, simply define the optional ",(0,s.jsx)(n.code,{children:"InitializeAsync"})," method:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"public class MyProvider : FeatureProvider\n{\n// diff-remove-block-start\n\n private ProviderStatus _status = ProviderStatus.NotReady; \n \n public override ProviderStatus GetStatus()\n {\n return _status;\n }\n\n public override Task Initialize(EvaluationContext context)\n// diff-remove-block-end\n// diff-add\n public override Task InitializeAsync(EvaluationContext context, CancellationToken cancellationToken = default)\n {\n // some async initialization\n }\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["For more details about this change, see our ",(0,s.jsx)(n.a,{href:"/blog/reconciling-with-state",children:"previous blog post"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"hook-authors",children:"Hook Authors"}),"\n",(0,s.jsx)(n.h4,{id:"hooks-must-return-valuetask",children:"Hooks Must Return ValueTask"}),"\n",(0,s.jsxs)(n.p,{children:["Hooks must now return ",(0,s.jsx)(n.code,{children:"ValueTask"})," instead of ",(0,s.jsx)(n.code,{children:"Task"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-csharp",children:"public class MyBeforeHook : Hook\n{\n// diff-remove-block-start\n public Task<EvaluationContext> Before<T>(HookContext<T> context,\n IReadOnlyDictionary<string, object> hints = null)\n {\n // code to run before flag evaluation\n }\n// diff-remove-block-end\n// diff-add-block-start\n public ValueTask<EvaluationContext> BeforeAsync<T>(HookContext<T> context,\n IReadOnlyDictionary<string, object> hints = null, CancellationToken cancellationToken = default)\n {\n // code to run before flag evaluation\n }\n}\n// diff-add-block-end\n"})}),"\n",(0,s.jsx)(n.h2,{id:"full-changelog",children:"Full Changelog"}),"\n",(0,s.jsxs)(n.p,{children:["See ",(0,s.jsx)(n.a,{href:"https://github.com/open-feature/dotnet-sdk/blob/main/CHANGELOG.md#200-2024-08-21",children:"here"})," for the full changelog."]}),"\n",(0,s.jsx)(n.h2,{id:"thanks",children:"Thanks!"}),"\n",(0,s.jsx)(n.p,{children:"Thanks to all our users, and a special thanks to everyone who contributed to the .NET SDK:"}),"\n",(0,s.jsx)("img",{src:a().A})]})}function u(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},28453(e,n,t){t.d(n,{R:()=>i,x:()=>l});var a=t(96540);const o={},s=a.createContext(o);function i(e){const n=a.useContext(s);return a.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:i(e.components),a.createElement(s.Provider,{value:n},e.children)}},83075(e){e.exports=JSON.parse('{"permalink":"/blog/dotnet-sdk-v2","editUrl":"https://github.com/open-feature/openfeature.dev/edit/main/blog/2024-08-21-dotnet-sdk-v2.md","source":"@site/blog/2024-08-21-dotnet-sdk-v2.md","title":"OpenFeature .NET SDK 2.0 Release","description":"Announcing the 2.0 Release of the OpenFeature SDK for .NET","date":"2024-08-21T00:00:00.000Z","tags":[{"inline":true,"label":".NET","permalink":"/blog/tags/net"},{"inline":true,"label":"dotnet","permalink":"/blog/tags/dotnet"},{"inline":true,"label":"c#","permalink":"/blog/tags/c"},{"inline":true,"label":"csharp","permalink":"/blog/tags/csharp"},{"inline":true,"label":"async","permalink":"/blog/tags/async"},{"inline":true,"label":"v2.0","permalink":"/blog/tags/v-2-0"},{"inline":true,"label":"v2","permalink":"/blog/tags/v-2"},{"inline":true,"label":"sdk","permalink":"/blog/tags/sdk"}],"readingTime":3.5,"hasTruncateMarker":true,"authors":[{"name":"Todd Baert","title":"Software Engineer at Dynatrace, OpenFeature maintainer","url":"https://github.com/toddbaert","imageURL":"https://github.com/toddbaert.png","key":"toddbaert","page":null}],"frontMatter":{"slug":"dotnet-sdk-v2","title":"OpenFeature .NET SDK 2.0 Release","date":"2024-08-21T00:00:00.000Z","authors":["toddbaert"],"description":"Announcing the 2.0 Release of the OpenFeature SDK for .NET","tags":[".NET","dotnet","c#","csharp","async","v2.0","v2","sdk"],"draft":false},"unlisted":false,"prevItem":{"title":"OpenFeature Contributor Spotlight: Andr\xe9 Silva","permalink":"/blog/andre-silva-spotlight"},"nextItem":{"title":"Join OpenFeature at WeAreDevelopers World Congress \'24","permalink":"/blog/wearedevelopers-2024"}}')}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.