Arda Çetinkaya Yazılım ve arada kendim ile ilgili karaladıklarım…

I think it is quite important to be proficient in the APIs of the “framework” or “library” that we work on, in addition to the language we use when developing applications. This enables us to easily provide certain requirements or to use the “framework” more effectively. With this approach, I will try to talk about a ITimeLimitedDataProtector API in ASP.NET Core which can be useful for creating secure or limited data models that may be needed for different scenarios.

Temporary data models or “text” expressions that are valid for only a certain period can sometimes be an approach that we need. Links sent for “Email Confirmation” or for resetting passwords during membership transactions may be familiar to many people. Or values such as codes that will be valid for a certain period in “soft-OTP” (One-time password) scenarios or “Bearer” tokens…etc.

Obviously, different methods and approaches are possible for such requirements. Without going into too much detail, I will try to briefly discuss how we can meet such needs in the .NET platform.

As you know, .NET and especially ASP.NET Core guide us with many APIs to meet the security needs of today’s applications. Strong encryption APIs, HTTPS concepts, CORS mechanisms, CRSF prevention, data protection, authentication, authorization, secret usage, and so on…


For the requirement I mentioned above, let’s look at the ITimeLimitedDataProtector interface in .NET under the “data protection” namespace. We can have some implementations for data or expressions that will only be valid for a certain period of time with the methods provided by this interface.

To use the methods of this interface, we first need the “Microsoft.AspNetCore.DataProtection.Extensions” package. Generally, this package is a library that exposes “data protection” features in .NET.

To use the “ITimeLimitedDataProtector” interface, we first need to create a “DataProtectionProvider”, and then define a protector that will protect our data with this “provider”.

var timeLimitedDataProtector = DataProtectionProvider.Create("SomeApplication")

When you look at the parameters of the methods here, the “string” expressions you see are important; they can be thought of as a kind of labeling for the created provider and DataProtectors. According to this labeling, the purpose and scope of data security are specified. These expressions are used in the creation of the keys that will be used to protect the data. Thus, a provider created with DataProtectionProvider.Create(“abc”) cannot access the expressions that ensure the security of a provider created in the form of DataProtectionProvider.Create(“xyz”).

When you look at the parameters of the DataProtectionProvider.Create() method, you can see that you can set some properties for protecting the data. You can specify a directory where the keys for data protection will be stored or that the keys will be encrypted with an additional certificate using X509Certificate2. I won’t go into too much detail about these, but what I want to emphasize here is that it is possible to customize data protection methods and change protection approaches with parameters.

In this way, we protect the expression we want to protect through the timeLimitedDataProtector variable we created by specifying a time interval with the Protect() method.

ProtectedData = timeLimitedDataProtector.Protect(plaintext: "HelloWorld"
                    , lifetime: TimeSpan.FromSeconds(LifeTime));

With the above expression, we are encrypting, hashing, and protecting the phrase “Hello World”. Our ProtectedData property becomes a structure similar to the following, which is valid for 20 seconds.

Time limit

We can specify any time duration in the form of TimeSpan with the lifetime parameter of the Protect() method, of course.

After protecting the encrypted and hashed expression, we can access the “Hello World” expression again by opening it with the Unprotect() method within 20 seconds as in this example. However, it is not possible to access this value after 20 seconds, and the data we protected loses its validity.

string data = timeLimitedDataProtector.Unprotect(protectedData);

It is not recommended to use data protection for a long or indefinite period of time with this API. The reason is the risk of maintaining the continuity of the keys used for encrypting and hashing the data when it is protected. If there are expressions that need to be kept under protection for a long time, it is possible to proceed with different methods, or different developments can be made according to our own needs using the interfaces provided by this API.

An important point is that only “text” expressions can be protected. Therefore, it is possible to protect slightly more complex data by “serializing” it (for example, using JsonSerializer).

To see the complete picture more clearly, let’s look at the below code of a Razor page model from an ASP.NET Core application as an example.

namespace SomeApplication.Pages
    using Microsoft.AspNetCore.DataProtection;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Text.Json;
    public class IndexModel : PageModel
        private readonly ILogger<IndexModel> _logger;
        public string ProtectedData { get; private set; }
        public string Data { get; private set; }
        public int LifeTime { get; private set; } = 300;
        public string Error { get; private set; }
        public IndexModel(ILogger<IndexModel> logger)
            _logger = logger;
        public void OnGet(string protectedData)
            var timeLimitedDataProtector = DataProtectionProvider.Create("SomeApplication")
            //prtecteddata variable is empty in URL
            if (string.IsNullOrEmpty(protectedData))
                //Let's have a simple data model as example
                var data = new SomeDataModel
                    Name = "Arda Cetinkaya",
                    EMail = "",
                    SomeDate = DateTimeOffset.Now
                //Let's serialize this simple data model
                string jsonString = JsonSerializer.Serialize(data, new JsonSerializerOptions
                    WriteIndented = true
                Data = jsonString;
                //Now let's protec the simple data model
                ProtectedData = timeLimitedDataProtector.Protect(plaintext: jsonString
                    , lifetime: TimeSpan.FromSeconds(LifeTime));
                //When URL have some variable value as ?protecdata=a412Fe12dada...
                    //Unprotect the protected value
                    string data = timeLimitedDataProtector.Unprotect(protectedData);
                    Data = "Data is valid";
                catch (Exception ex)
                    Error = ex.Message;
    public class SomeDataModel
        public string Name { get; set; }
        public string EMail { get; set; }
        public DateTimeOffset SomeDate { get; set; }

In the example above, we are protecting a JSON expression for 20 seconds and associating it with a link. The link will be valid for 20 seconds and the value we have protected will be valid as well. However, after 20 seconds, the protected data will expire and lose its validity.

This simple and quick writing, after a long break, will hopefully benefit me and open a door for you to clear up some question marks and provide you with some benefits in your various solutions. See you in the next article.

I have written this post and publish as Turkish before. This is english translated version of that post.

There is some curse on the software development processes. A curse that everyone knows but cannot escape. “Assumptions”

Assumption: a thing that is accepted as true or as certain to happen, without proof.

Oxford Languages

During design and implementation processes of software development projects, assumptions are made. These might be somehow normal. But if these assumptions are not based on some data or they are not known as same for every stakeholder in the project team, then assumptions turn into a dark curse and this dark curse might show its worst side without knowing when.

If we have a problem or a requirement in our business model, we also expect to solve it in a consistent way. When we think that we solved the problem but if it is happing again then it’s obvious that we couldn’t solve it as we expected. To minimize this issue and to have a consistent solution we use help of software solutions.

But with assumptions, sometimes, we are implementing these software solutions very hard and complex. This complexity is causing some other problems or solution times become too long. And as you know, these are not expected or wanted outcomes in any business.

We live in an era where change is inevitable and there are many unknown parameters. This fact is the best friend of assumptions. When we have a problem or requirement and if there are too many parameters, sometimes finding suitable values for parameters can be a difficult or time-consuming task for this era. But we must come up with a solution somehow, and this is where assumptions come into play. It’s not so bad, even necessary to make assumptions if we can base assumptions on some data. As all we know, we solved lots of mathematical questions in school, assuming x is equal to 3 or x is between 0-9.

Have some data…(at least a little)

We need to make assumptions according to some data. And then doing the implementations according to these data-based assumptions will easier and more legit. The outcome of assumptions won’t be a surprise. Doing a development with non-data-based assumption can create some output. But the consistency of this output will remain unknown. And this will create a risk in the solution as in the business.

So, we need to try to support our assumptions with some data in software solutions. Monitoring and gathering data, some proof-of-concept data or having answers for questions are the main source of data. And these are not one time job, should be done continuously while software solutions live.

Do documentation…

Everybody might have their own assumptions. If these assumptions are not documented well and not shared/known by some other stakeholder, then they are the potential root causes of some upcoming problems. There are some cases that some assumptions are made on other non-data-based assumptions. And if there is no additional document or data about these assumptions, these might be a grenade with pulled pin. Or every point of view of solution might be different. And this is not a good thing for consistency.

So, there should be some documentation for assumptions. Within this documentation, the reason and validity of the assumption should be described. It is crucial to make this document is up to date.

Don’t cause over-engineering…

Software developers are(might 😁) more focused on the solution than the problem time to time. Sometimes any kind of approach(?) might be implemented for the requirement/problem without thinking the exact problem. Because of this we have this “over-engineering” idiom in terminology. And when assumptions join with love of doing fancy things results might not be as expected. And, if we have “overestimation” as a side-dish then I guarantee that there is going to be some errors and problems in the journey.

Because of assumptions, unnecessary implementations and high complexity in code base will start to exist. And I am not sure if this is a good for any code base. Making everything as generic feature, unnecessary configurations or violation of YAGNI(You aren’t gonna need it) principle is just some basic example outcomes of making assumptions.

So, within implementation process, if we have questions or unknowns, instead of making assumptions we need to try to find answers for these. Because of the project’s current state maybe it is very hard to find answers. With above data and documentation approach we can have some assumptions with tests. If we can have tests for our assumed implementations, then it will be easier to manage assumptions.

Somehow assumptions can be inevitable. If they are inevitable, then we need to know how to handle them or know to make a good assumption. Briefly to have a consistent software solution;

  • Make assumptions based on some data.
  • Document your assumptions.
  • Test and try to validate your assumptions.

See you on next post, until then happy coding. And remember just because the sun has risen every day, it doesn’t mean that every day is bright. 🤓

Event-driven architecture is one of the patterns in software development for decoupled and distributed services. I am not going to deep dive into what is it or not. But I will try to share some initial info for an existing cloud service in AWS that might empower our event-driven solutions.

Those who follow my posts probably know that I am more interested in Azure. But because learning new things is fun(😍) and because of some other “real-life” requirements, time to time I need to work with other cloud providers. Let’s try to understand our options in the cloud world for building good solutions.

Amazon EventBridge is a fully managed, serverless and scalable event routing service (a.k.a service-bus). It provides easy ways to connect with some applications and with other AWS services. Briefly, it provides us to build solutions with event-driven architectures in the cloud without thinking too deeply. Filtering, transforming message, routing the messages, re-try or archive things are all managed and easy within AWS EventBridge.

Let me share some simple scenarios to make it a little bit clearer why we can need AWS EventBridge for who have never used it before.

For example, sometimes in an e-commerce web site, some items are out-of-stock. When items are out-of-stock, automatically a button appears. And we click that button which is letting us know when items are in stock again. In that case when items are store’s inventory again, the system might notify us automatically.

This is all happens automatically within some flow which is triggered by an action/data with or without human interaction in almost real time. With AWS EventBridge it is efficient, reliably and easy to build this kind of or more complex solutions.

AWS EventBridge has some core blocks.

Event Buses

Event Buses are the services that receive events from some sources and stream them for rules. By default, there is a “default” event bus. There is possible to create some other custom buses, but there are some limitations for them. For example, if you are going to have scheduled rule, it can only be run in “default” event bus. Basically, custom event buses might be preferable for custom applications.

With some additional permissions for Event Buses, it is possible to have events from some other AWS accounts and from some another region.


Rules are like decision units which matches the incoming events to defined pattern. When a rule is defined, there are some settings needed to be done. Some matching pattern is defined so that incoming events can be filtered. For example, a rule can be defined with an AWS S3(a.k.a storage service) pattern so if a S3 is set to publish some events, this rule can match this event according to defined pattern. And with some advanced context filtering it is possible to filter according to the resource name.

It might be a little irrelevant with events, but it is also possible to create scheduled rule with AWS EventBridge. With some scheduled time, settings, it is possible to trigger some other resources. Now there is a new AWS EventBridge Scheduler, it is basically the same with current things, but a little bit advanced in background. But it’s obvious that “naming things” is hard.😁😁😁


Targets are like the execution units for the events’ data. They are defined in Rules. So, if a rule match the event data, it triggers a resource. A resource can be another AWS EventBridge event bus or AWS resource. For example, when a file is uploaded into the AWS S3, another AWS services like AWS Lambda (a.k.a serverless function), EC2(a.k.a VM) can be triggered. There are lots of options for targets in AWS EventBridge rules. A rule can contain more than one target. If the rule matches the event data, the targets are executed in parallel.

As I described above; AWS EventBridge is fully managed event routing service, so that some additional features are provided as built in. For example, with some simple settings it is possible to define re-try policies. Number of hours (default is 24h) to keep unprocessed events or max. re-try number (default is 185) like of settings can be done within Rules.

And within AWS EventBridge, it is possible to archive and re-sent events when it’s needed. It might be thought as an “Event Store” in some ways.

All events need some defined structures to have a reliable data. AWS EventBridge has schema registry support by default. All AWS services events schemas are defined there. So, it is very easy to adopt these schemas for custom built applications. And it is possible to create our own schemas for our own custom events. So, event buses can ingest those events.

Let’s go over with AWS Console to make these a little bit clearer.

Read more…

Rate limiting is an approach to limit resources that can be accessed over a time. Limiting might not sound a good word in the beginning, but we are living in a world with huge consumption rates within limited resources. And this is not so different for software solutions. So, limiting some resources may be required for reliable and secure software solutions in this era.

And also, within some business requirements, limiting resources may help organizations to get some revenue for their owned resources.

In either way, limiting resources provides some benefits. In fact, this concept is not a new thing in software solutions and Microsoft’s development stack. It could be already done with some libraries or custom implementations. But within new version of .NET 7, “RateLimiting” has been introduced as built-in for .NET developers to have easier development to protect owned resources.

Within this post; because of, we live in a big world-wide connected web, I will try to give a brief introduction for “RateLimiting” middleware in ASP.NET Core. But to make a deep dive into RateLimiting concept in .NET 7, I suggest you to check also new APIs in System.Threading.RateLimiting package. This is also the core component of RateLimiting middleware for ASP.NET Core.


Microsoft.AspNetCore.RateLimiting package provides a rate limiting middleware for an ASP.NET Core applications.

We can add this package to our web application to have some rate limiting approaches in our applications. After then to add RateLimiting middleware, we use UseRateLimiter() extension method for IApplicationBuilder (WebApplication)

    new RateLimiterOptions()
        OnRejected = (context, cancellationToken) =>
            context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;

                .ForEach(m => app.Logger.LogWarning($"Rate limit exceeded: {m.Key} {m.Value}"));

            return new ValueTask();
        RejectionStatusCode = StatusCodes.Status429TooManyRequests


Within this method it is possible to have some option to have control over RateLimiting, such as; setting HTTP status code and implementing some custom actions when rate limit is occurred and also some other limiters(a.k.a Rate limiting algorithms)

For example, the above example demonstrates setting HTTP429 as response code and a delegate to do some logging with some metadata when rate limiting is occurred.

HTTP 429 is default status code for too many requests. So, it is crucial to use this status code for the sake of pandas’ health and planet. Let’s expand our awareness within HTTP codes.🐼

And then we need to add some limiters to our rate limiting option so that we can define rate limiting algorithms. There are some built-in rate limiting algorithms provided by .NET 7. And these are provided as extension methods for RateLimiterOptions.

Read more…

Öncelikle “telemetry” ne demek bununla başlayalım. Genel kullanım olarak telemetre şeklinde dilimizde yer edinen bu kavram, dilimizde de aslında “uzaktan ölçüm” anlamı ile tanımlanmakta. Sürdürülebilir yazılım çözümleri için önemli olan “ölçüm” ya da “ölçmek” konuları; günümüzün hızla “değişen” ve “büyüyen” ihtiyaçlarına sağlanan çözümler için daha da farkında olunması gereken ve önemi artan konuları oldu. Sunulan çözümlerin ölçülebilir olması ve bu ölçüm değerlerinin monitör edilebilir olması çözümlerin kalite gereksinimlerini karşılamak ve çözümlere değer katabilmek için oldukça kritik bir nokta oynuyor. Malum ölçemediğimiz bir çözümü geliştirmek ve ileri götürmek biraz zor

Bir yazılım çözümünün gereksinim duyduğu hafıza, işlem gücü, çalışma süreleri, çalışma anındaki kayıtlar(log’lar) ya da iletişim süreleri gibi gibi başlıklar, çözümlerin davranışlarına ve gelişmelerine büyük katkı sağlıyor.  

Özellikle artık dağıtık mimarilerin bir norm olduğu zamanda yaşıyoruz ve bu dağıttığımız bileşenlerin hayat döngülerini monitör edebilmemiz oldukça önemli. Yazılım teknolojileri için bu alanda da tahmin edebileceğimiz gibi birçok araç var. Sürekli gelişen ve büyüyen yazılım dünyası için yine tahmin edebileceğiniz gibi bu araçların sayısı ve olgunlukları her geçen gün değişiyor. Bu araçların uygulama yöntemleri de değişiklik gösterdiği için yazılım çözümleri içerisinde genel bir yaklaşım ile kullanmak biraz zor, -ki zaten standart yaklaşımların yazılım dünyasında oluşması(?) biraz zor.  

OpenTelemetry nedir?

İşte bu noktada OpenTelemetry projesi hayatımıza giriyor. Farklı firmaların ya da organizasyonların, farklı yöntemler ile geliştirdiği ve sunduğu araçlara direkt bağlı kalmadan, firma/organizasyon bağımsız API’ların, araçların, SDK’ların sunulduğu, kısmen yeni bir proje, OpenTelemetry. Açık kaynak olarak geliştirilen ve birçok popüler benzer araçlar tarafından da desteklenen bir proje olması ile de bir noktada hepimizin karşılaşacağı bir proje olacak.  

Bulut platformlarındaki teknolojilerin olgunluk ve tercih edilme kriterleri için kılavuz olabilecek “Cloud Native Computing Foundation” içerisinde şu an kuluçka döneminde gelişimini sürdüren bir proje statüsünde. 1-2 yıl içinde de mezuniyeti bekliyorum şahsen.🎓 😀

 Başlıktan direkt sadece .NET platformu ile ilgili bir proje olduğu algısı oluşmuş olabilir. Ama şimdiden onu da belirtim ki, direkt bir alakası yok. Python, Go, Java, JavaScript gibi gibi birçok yazılım dili teknolojileri için de geçerli bir proje ama tabi ki olgunlukları ne yazık ki aynı değil. Açık kaynak geliştirme dünyasının bir ürünü olduğu için gelişme ve olgunlaşma durumları aynı değil. Bu yüzden dikkatli olmak ve “neden-sonuç” ilişkisini iyi tartarak bu projeye yaklaşmak önemli. 

“Madem direkt alakalı değil, niye .NET de yazdın?” diyecek olanlar için biraz daha sabırlı olmalarını isteyeceğim. OpenTelemetry‘nin yazılım çözümlerini ölçerken baz aldığı kavramlar ve bu kavramların.NET platformundaki karşılıklarından bahsederek yazının başlığındaki .NET kısmına geleceğim. 

OpenTelemetry, yazılım çözümlerinin ölçümünü için 3 ana başlık altında topluyor. Bu arada OpenTelemetry‘nin bu başlıkların hepsi için de ayrı ayrı spesifikasyon da sunuyor ki, bu şartlara göre bu ölçüm değerlerinin ve ölçüm işlerinin yapılmasını hedefliyor. 

En basit anlatım olarak, OpenTelemetry‘nin sağladığı API’lar ve SDK’ler ile bu başlıklar altındaki verileri toplayıp gereken araçlara aktarmak mümkün, -ki birazdan bunlarla ilgili bir örnek ile daha iyi anlayacağız. Ama bir başlıktan daha bahsetmek istiyorum; 

OpenTelemetry Collector

OpenTelemetry projesinden ilk bahsederken, “…API’ların, araçların, SDK’ların sunulduğu…” demiştim hatırlarsınız. OpenTelemetry Collector, buradaki “araçlar” kısmı. OpenTelemetry Collector, yukarıdaki başlıklar altındaki ölçüm verileri toplayıp, konfigürasyonla başka istenen araçlara aktarabiliyor. Mesela Trace verilerini ya ister Jaeger‘e ya da ister Zipkin‘e uygulama tarafında bir değişiklik yapmadan görüntülemek mümkün.  

OpenTelemetry Collector‘ın önemli olduğu ve fayda sağladığı nokta bu uçtan uca akışı sadece konfigürasyon yönetimi ile sağlıyor olması. Aşağıdaki görsel üzerinden yapıyı biraz daha anlamaya çalışalım. Görsel OpenTelemetry‘nin ölçüm değerlerini nasıl toplandığı biraz olsun anlamak için oldukça iyi. 

OpenTelemetry Collector

Sol tarafta dikkat edersiniz ki; receiver(alıcı) bileşenleri var. Bu Kafka, PostgreSql, OTLP, RabbitMQ, Redis, MySql… gibi gibi birçok aracı destekleyen OpenTelemetry bileşenleri olabilir. Hepsi açık kaynak bir şekilde geliştirilmekte. Buradan ulaşabilirsiniz ve hatta isterseniz daha derinlere dalıp kendi alıcınızı da yazabilirsiniz. Bu alıcılar ilgili araçların çeşitli API’larını ya da parçalarını dinleyip ya da sorgulayıp ilgili verileri alıyorlar. Mesela, RabbitMQ alıcısı; RabbitMQ’nun Management API’larını dinleyip ondan gelen verileri iletiyor. 

Görselde dikkatinizi OTLP diye bir kutucuk dikkatinizi çekecektir. Bu OpenTelemetry Protocol (OTLP)’üne denk gelen bir gösterim. Yani; ölçüm verilerinin kaynak ve alıcılar arasında standart bir şekilde olmasını sağlayacak bir protokol. Bu protokol ile verilerin sıkıştırılması, iletişimdeki re-try mekanizmaları, şifreleme gibi aktarım yöntemleri sağlanmakta. Log, Metric ve Trace kavramları için aynı olgunlukta değil ama standartlaşma(?) açısından ışık veren bir protokol.   

OpenTelemetry ne değildir? 

Açıkcası burada OpenTelemetry’nin uygulamalardaki ölçüm verilerinin oluşturulması ve dağıtılması ile ilgili ara bir bileşen olduğunun altınızı çizmek isterim. Bu verilerin saklanması, sorgulanması ya da gösterilmesi gibi görevleri yok. Çok fazla veri üretilmesine yol gösteren bir proje olduğundan ister istemez böyle bir beklenti içinde de olabiliyor insan. Özellikle belirtmek istedim… 

Çok konuştun biraz da kod göster… 

Daha değil ama az kaldı. Önce hızlıca yazının başlığında .NET tarafına yaklaşalım. OpenTelemetry’nin yukarda bahsettiğim 3 ana başlığı, .NET platformundaki OpenTelemetry API’ları için nasıl işletiliyor önce bunu anlamaya çalışalım. 

Takip etme (a.k.a Tracing) 

OpenTelemetry, Tracing verileri için .NET platformunda System.Diagnostics.* namespace’ine bel bağlamış durumda, -ki aslında zaten System.Diagnostics.* .NET Framework zamanından beri belli “trace” verilerinin oluşmasını zaten built-in olarak sağlıyordu. Yeni .NET platformu ile de biraz daha güncel ihtiyaçlara uygun hale geldiğini söyleyebilirim 

Uygulama kayıtları(a.k.a Logging) 

OpenTelemetry, loglama ölçüm verileri için de .NET’in Microsoft.Extensions.Logging.Abstractions namespace‘ine bel bağlamış durumda. Yani standart olarak .NET platformunun sağladığı Logging ara yüzleri ve metotları ile geliştirdiğimiz uygulama loglama alt yapılarında üretilen veriler OpenTelemetry ile diğer ölçüm değerleri ile ilişkilendirilebilmekte. Ama eğer uygulama loglama alt yapımızı .NET’in standart sağladığı yapıların üzerine kurmadıysak uygulama logları ile ölçüm değerleri ilişkilendirmek pek mümkün değil. Bu arada ne demek istiyorum biraz daha açsam iyi olacak sanırım; uygulama içinde yazdığımız log’ların hangi talep ile ilişkili olduğunun tutulması gibi diyebilirim. Sorguların CorelationID ya da TraceID‘leri ile o sorgu sırasında yazılan uygulama loglarının ilişkilendirilmesi gibi. 

Ölçü sistemi (a.k.a Metrics) 

Metric ölçüm verileri için yine System.Diagnostics.* namespace‘i kullanılmakta. Meter, Counter gibi sınıflar ile uygulamamızda ölçmek isteğimiz metrik değerlerini oluşturabiliyor ve ölçebiliyoruz. Mesela bir metotun, hangi parametre ile kaç kere çağrıldığı ya da bir metot kaç kere hata fırlatmış bunları ölçmek için metrikleri yine System.Diagnostics.* ile yapabiliyoruz. OpenTelemetry‘nin bileşenleri de bunlar üstünden bu metrik verilerini dışa aktarabiliyor. Bu arada eğer zaten dotnet counters aracına aşinaysanız ne demek istediğimi anlamışınızdır.  

Birazdan göstermeye çalışacağım örnekte uygulama metrikleri ile ilgili bir gösterim olmayacak. Yazı fazla uzadığından onunla alakalı ayrı bir yazı yakında paylaşacağım.

Dikkat edersiniz ki, herhangi bir OpenTelemetry ya da başka bir bileşene sahip olmadan zaten var olan .NET platform bileşenleri ile ölçüm değerleri oluşturulabiliyor. Bu noktada OpenTelemetry bunları bir araya getirip anlamlı bir hale sokuyor diyebiliriz.