<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[.NET Core - Medium]]></title>
        <description><![CDATA[Posts related to .NET Core (ASP.NET Core, MVC, Web API…) - Medium]]></description>
        <link>https://medium.com/net-core?source=rss----9864284e8ea3---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>.NET Core - Medium</title>
            <link>https://medium.com/net-core?source=rss----9864284e8ea3---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 03:53:43 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/net-core" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Apache Kafka with .NET: From Core Concepts to a Working Application]]></title>
            <link>https://medium.com/net-core/apache-kafka-with-net-from-core-concepts-to-a-working-application-9b942926a34a?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/9b942926a34a</guid>
            <category><![CDATA[dotnet-core]]></category>
            <category><![CDATA[kafka-producer]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[kafka-consumer]]></category>
            <category><![CDATA[apache-kafka]]></category>
            <dc:creator><![CDATA[Sena Kılıçarslan]]></dc:creator>
            <pubDate>Fri, 24 Jul 2026 15:47:03 GMT</pubDate>
            <atom:updated>2026-07-25T09:30:35.703Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post, I will demonstrate how to use Kafka in .NET after an introduction to the key concepts.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zAEI8g62E9Uvdk7D02UH_g.png" /></figure><p>Specifically, we’ll cover:</p><ul><li>A brief introduction to Apache Kafka and its core concepts.</li><li>Setting up a local Kafka cluster using Docker.</li><li>Running Kafka UI to inspect brokers, topics, and messages.</li><li>Creating a topic</li><li>Creating a Kafka Producer in .NET.</li><li>Creating a Kafka Consumer in .NET.</li><li>Publishing and consuming movie events.</li></ul><p>By the end of this article, you’ll have a working Kafka environment running locally and a basic understanding of how Kafka applications are built with .NET.</p><p>I will use the following tools:</p><ul><li>Visual Studio 2026</li><li>.NET 10</li><li>Docker Desktop</li><li>Git</li></ul><p>And the following technologies:</p><ul><li>Apache Kafka</li><li>Kafka UI by Provectus</li><li>Confluent.Kafka (.NET client library)</li><li>Docker Compose</li></ul><h3>Apache Kafka</h3><p>Apache Kafka has become one of the most widely adopted event streaming platforms for building scalable, reliable, and event-driven applications. It enables applications to exchange data asynchronously, allowing producers and consumers to operate independently while handling millions of events per second.</p><p>Although Kafka is widely used in distributed systems and microservices, getting started can seem overwhelming due to the number of concepts involved, such as brokers, topics, partitions, offsets, consumer groups, and replication.</p><p>So, let&#39;s start with understanding the key concepts.</p><h3>Kafka Key Concepts</h3><p>The following diagram illustrates the main Kafka components and how they interact.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SOA7onMqEe6nMkkrxq5JUQ.png" /></figure><h4>Producer</h4><p>A <strong>Producer</strong> is an application that publishes events to Kafka.</p><p>Unlike traditional request-response communication, producers do not send messages directly to consumers. Instead, they publish events to Kafka, allowing multiple independent consumers to process those events whenever they become available.</p><h4>Consumer</h4><p>A <strong>Consumer</strong> is an application that subscribes to one or more Kafka topics and continuously reads events from them.</p><p>Multiple consumers can read the same topic independently. Alternatively, consumers can work together as part of a <strong>Consumer Group</strong>, allowing the processing workload to be distributed across multiple application instances.</p><h4>Topic</h4><p>A <strong>Topic</strong> is a named stream of events. You can think of it as a durable log where related events are stored.</p><p>Kafka topics have several important characteristics:</p><ul><li>A topic is a <strong>durable log of events</strong>.</li><li>Topics are <strong>append-only</strong>, meaning new messages are always written to the end of the log.</li><li>Messages stored in a topic are <strong>immutable</strong> once written.</li><li>The <strong>retention period</strong> is configurable, allowing messages to be retained for hours, days, weeks, or even indefinitely.</li></ul><blockquote>One important thing to remember is that consuming a message does <strong>not</strong> remove it from the topic. Messages remain available until their configured retention period expires.</blockquote><h4>Event (Message)</h4><p>An <strong>Event</strong> (also called a <strong>Message</strong> or <strong>Record</strong>) represents a single piece of information stored in Kafka.</p><p>Every Kafka message consists of four parts:</p><p><strong>Timestamp</strong></p><p>The time when the event was created or appended to Kafka.</p><p><strong>Key</strong></p><p>Used to determine which partition stores the message. The key does <strong>not</strong> have to be unique.</p><p><strong>Value</strong></p><p>The actual payload of the event.</p><p><strong>Headers</strong></p><p>Optional key-value pairs containing metadata such as correlation IDs, tracing information, or custom application data.</p><h4>Broker</h4><p>A Broker is a computer, virtual machine, cloud instance, or Docker container running the Kafka broker process.</p><p>A Kafka cluster typically consists of multiple brokers working together.</p><p>Each broker is responsible for:</p><ul><li>Managing the partitions stored on the broker.</li><li>Handling write requests from producers.</li><li>Handling read requests from consumers.</li><li>Managing replication of partitions for fault tolerance.</li></ul><p>To ensure high availability, each partition is replicated across multiple brokers.</p><p>For every partition:</p><ul><li>One replica acts as the Leader.</li><li>The remaining replicas act as Followers.</li></ul><p>Under normal operation:</p><ul><li>Producers write to the Leader.</li><li>Consumers read from the Leader.</li><li>Followers continuously replicate data from the Leader.</li></ul><p>If the Leader becomes unavailable, Kafka automatically promotes one of the synchronized Followers to become the new Leader.</p><p>This leader-follower model is one of Kafka’s key mechanisms for providing fault tolerance and high availability.</p><blockquote>The <strong>Controller</strong> is a special Kafka broker responsible for managing the Kafka cluster itself. It does <strong>not</strong> store application data or handle producer and consumer requests. Instead, it manages the cluster’s metadata and coordinates administrative operations.</blockquote><h4>Consumer Group</h4><p>A Consumer Group is a collection of consumers working together to process events from a topic.</p><p>Kafka distributes partitions among consumers within the same group so that each partition is processed by only one consumer.</p><p>As more consumers are added to the group, Kafka automatically rebalances partition ownership, allowing applications to scale horizontally.</p><h4>Control Plane (ZooKeeper / KRaft)</h4><p>Kafka clusters require a control plane responsible for managing cluster metadata and coordinating brokers.</p><p>Historically, Kafka relied on Apache ZooKeeper for these tasks. Modern Kafka versions introduce KRaft (Kafka Raft Metadata mode), which replaces ZooKeeper with a built-in metadata quorum.</p><p>Regardless of which option is used, the responsibilities are the same:</p><ul><li>Managing broker membership.</li><li>Tracking the Leader for every partition.</li><li>Storing topic and partition metadata.</li><li>Coordinating controller elections.</li><li>Managing overall cluster metadata.</li></ul><p>New Kafka deployments are recommended to use KRaft, while many existing production environments still rely on ZooKeeper.</p><h4><strong>Advantages of Kafka</strong></h4><ul><li>Kafka stores messages for a configurable retention period, allowing consumers to read messages at any time within that window.</li><li>Kafka consumers can replay messages from any offset, making it easy to reprocess data.</li><li>Kafka supports consumer groups for load balancing and parallel processing.</li><li>Kafka provides message ordering within partitions.</li><li>Kafka can be run on-premises or in any cloud.</li><li>Kafka integrates with Kafka Streams and ksqlDB for real-time stream processing.</li></ul><p>Now the fun part starts😀</p><p>We will set up our local Kafka and Kafka UI and then will create a producer application which sends messages to the movies topic and a consumer application that reads messages from this topic.</p><h3>Setting up Local Kafka and Kafka UI</h3><p>Create a docker-compose.yml with the following contents:</p><pre>services:<br>  kafka:<br>    image: apache/kafka:latest<br>    container_name: kafka<br>    hostname: kafka<br>    ports:<br>      - &quot;9092:9092&quot;<br>    environment:<br>      KAFKA_NODE_ID: 1<br>      KAFKA_PROCESS_ROLES: &quot;broker,controller&quot;<br>      KAFKA_CONTROLLER_QUORUM_VOTERS: &quot;1@kafka:29093&quot;<br>      KAFKA_LISTENERS: &quot;CONTROLLER://:29093,PLAINTEXT_HOST://:9092,PLAINTEXT://:19092&quot;<br>      KAFKA_ADVERTISED_LISTENERS: &quot;PLAINTEXT_HOST://localhost:9092,PLAINTEXT://kafka:19092&quot;<br>      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: &quot;CONTROLLER:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT,PLAINTEXT:PLAINTEXT&quot;<br>      KAFKA_CONTROLLER_LISTENER_NAMES: &quot;CONTROLLER&quot;<br>      KAFKA_INTER_BROKER_LISTENER_NAME: &quot;PLAINTEXT&quot;<br>      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1<br>      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1<br>      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1<br>      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0<br>      KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: 1<br>      KAFKA_SHARE_COORDINATOR_STATE_TOPIC_MIN_ISR: 1<br>      KAFKA_LOG_DIRS: &quot;/tmp/kraft-combined-logs&quot;<br>      CLUSTER_ID: &quot;4L6g3nShT-eMCtK--X86sw&quot;<br><br>  kafka-ui:<br>    image: provectuslabs/kafka-ui:latest<br>    container_name: kafka-ui<br>    depends_on:<br>      - kafka<br>    ports:<br>      - &quot;8080:8080&quot;<br>    environment:<br>      KAFKA_CLUSTERS_0_NAME: local<br>      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:19092</pre><p>Then run the following command:</p><pre>docker compose up -d</pre><p>Now you will see 2 running containers in your Docker Desktop.</p><ul><li>kafka</li><li>kafka-ui</li></ul><p>We are using <a href="https://github.com/provectus/kafka-ui">UI for Apache Kafka</a> tool and you can open it from Docker Desktop using the <strong>kafka-ui</strong> container.</p><p>Now, let’s create the movies topic from the Kafka UI.</p><p>Go to Topics and select <strong><em>Add a Topic</em></strong>. Then fill the values as you want and click <strong><em>Create topic</em></strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZYaYRkCeItbZXpQsU5w9QQ.png" /></figure><p>We can see the created topic as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Wfg03jp45J-Iq7xRtKTklA.png" /></figure><h3>Creating a Producer Application</h3><p>Now that our Kafka cluster is running and the movies topic has been created, it is time to build the first .NET application: the <strong>Producer</strong>.</p><p>The Producer application will create movie events and publish them to Kafka. In our example, the producer will send movie data to the movies topic.</p><p>Open Visual Studio and create a new <strong>Console App</strong> project. Name it something like KafkaProducer.</p><p>After the project is created, click <strong><em>Dependencies -&gt; Manage Nuget Packages… </em></strong>and add Confluent.Kafka package.</p><blockquote>The Confluent.Kafka package is the official and most commonly used Kafka client for .NET applications.</blockquote><p>We will need to have some configuration related to our Kafka instance. Create Configuration folder under the project and add KafkaConfiguration class there with the following code:</p><pre>namespace KafkaProducer.Configuration;<br><br>public class KafkaConfiguration<br>{<br>    public string BootstrapServers { get; set; } = string.Empty;<br>}</pre><p>Lets use appsettings.json to store our Kafka configuration values. Create this file under the project and add the address of the Kafka container in your Docker Desktop.</p><pre>{<br>  &quot;KafkaConfiguration&quot;: {<br>    &quot;BootstrapServers&quot;: &quot;localhost:9092&quot;<br>  }<br>}</pre><p>Before publishing events to Kafka, we need a simple model to represent a movie.</p><p>Create Models folder under the project and then add a file named Movie.cs there with the following code:</p><pre>namespace KafkaProducer.Models;<br><br>public class Movie<br>{<br>    public long Id { get; set; }<br>    public string Title { get; set; } = string.Empty;<br>    public string Director { get; set; } = string.Empty;<br>    public int ReleaseYear { get; set; }<br>    public string Genre { get; set; } = string.Empty;<br>    public double Rating { get; set; }<br>}</pre><p>Now let’s add the ProducerService to our application. Create Services folder under the project and add the ProducerService there with the following code.</p><pre>using Confluent.Kafka;<br>using KafkaProducer.Configuration;<br><br>namespace KafkaProducer.Services;<br><br>public class ProducerService&lt;T&gt; where T : class<br>{<br>    private readonly IProducer&lt;string, T&gt; _producer;<br><br>    public ProducerService(KafkaConfiguration kafkaConfiguration)<br>    {<br>        var producerConfig = new ProducerConfig<br>        {<br>            BootstrapServers = kafkaConfiguration.BootstrapServers,<br>            Acks = Acks.All<br>        };<br><br>        _producer = new ProducerBuilder&lt;string, T&gt;(producerConfig)<br>            .SetValueSerializer(new JsonSerializer&lt;T&gt;())<br>            .Build();<br>    }<br><br>    public async Task ProduceMessageAsync(string topic, string key, T value, IEnumerable&lt;Header&gt;? headers = null)<br>    {<br>        try<br>        {<br>            var message = new Message&lt;string, T&gt;<br>            {<br>                Key = key,<br>                Value = value<br>            };<br><br>            if (headers != null)<br>            {<br>                message.Headers = [.. headers];<br>            }<br><br>            var deliveryResult = await _producer.ProduceAsync(topic, message);<br><br>            Console.WriteLine($&quot;Message delivered to {deliveryResult.TopicPartitionOffset}&quot;);<br>        }<br>        catch (ProduceException&lt;string, T&gt; e)<br>        {<br>            Console.WriteLine($&quot;Delivery failed: {e.Error.Reason}&quot;);<br>        }<br>    }<br>}<br><br>public class JsonSerializer&lt;T&gt; : ISerializer&lt;T&gt;<br>{<br>    public byte[] Serialize(T data, SerializationContext context)<br>    {<br>        return System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(data);<br>    }<br>}</pre><blockquote>Acks.All means the leader broker will wait for all in-sync replicas to acknowledge the message. This provides the highest level of durability: the message is only considered &quot;written&quot; when all replicas have it.</blockquote><p>Now let&#39;s send a message to the movies topic using this class. <br>Please add the following code in your Program.cs</p><pre>using KafkaProducer.Configuration;<br>using KafkaProducer.Models;<br>using KafkaProducer.Services;<br>using Microsoft.Extensions.Configuration;<br><br>KafkaConfiguration kafkaConfig = GetConfiguration();<br><br>var producerService = new ProducerService&lt;Movie&gt;(kafkaConfig);<br><br>var topicName = &quot;movies&quot;;<br><br>Movie movie = CreateMovie();<br><br>await producerService.ProduceMessageAsync(topicName, movie.Id.ToString(), movie);<br><br>static KafkaConfiguration GetConfiguration()<br>{<br>    var builder = new ConfigurationBuilder()<br>        .AddJsonFile(&quot;appsettings.json&quot;)<br>        .AddEnvironmentVariables();<br><br>    var configuration = builder.Build();<br><br>    var kafkaConfig = new KafkaConfiguration();<br>    configuration.GetSection(&quot;KafkaConfiguration&quot;).Bind(kafkaConfig);<br>    return kafkaConfig;<br>}<br><br>static Movie CreateMovie()<br>{<br>    return new Movie<br>    {<br>        Id = 1,<br>        Title = &quot;A Separation&quot;,<br>        Director = &quot;Asghar Farhadi&quot;,<br>        ReleaseYear = 2011,<br>        Genre = &quot;Drama&quot;,<br>        Rating = 8.3<br>    };<br>}</pre><p>Now run the <strong>KafkaProducer</strong> application.<br>You should get a success message in the Console.<br>Also, we can see the message sent in the Kafka UI as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k9rF6KGPIVif3Z4YxygC_Q.png" /></figure><h3>Creating a Consumer Application</h3><p>Our producer successfully published a movie event to Kafka. Now it’s time to build a <strong>Consumer</strong> application that listens to the movies topic and processes incoming events.</p><p>Unlike the producer, which publishes data, a consumer continuously waits for new events and processes them as they arrive.</p><p>Create another <strong>Console App</strong> project in your current solution in Visual Studio and name it <strong>KafkaConsumer</strong>.</p><p>Just like the producer application, install the Confluent.Kafka package.</p><p>We will reuse some of the classes/files from the Producer project. Copy Configuration and Models folders with their classes inside (<em>remember to change the namespaces</em>). Also copy the appsettings.json to this project.</p><h4>ConsumerService</h4><p>Now create Services folder and create ConsumerService class under it with the following code:</p><pre>using System.Text.Json;<br>using Confluent.Kafka;<br>using KafkaConsumer.Configuration;<br>using KafkaConsumer.Models;<br>using Microsoft.Extensions.Options;<br><br>namespace KafkaConsumer.Services;<br><br>public class ConsumerService<br>{<br>    private readonly IConsumer&lt;string, byte[]&gt; _consumer;<br>    public readonly KafkaConfiguration _kafkaConfig;<br><br>    public ConsumerService(IOptions&lt;KafkaConfiguration&gt; kafkaOptions)<br>    {<br>        _kafkaConfig = kafkaOptions.Value;<br>        var topicName = &quot;movies&quot;;<br><br>        var config = new ConsumerConfig<br>        {<br>            BootstrapServers = _kafkaConfig.BootstrapServers,<br>            GroupId = &quot;movie-consumer-group&quot;,<br>            AutoOffsetReset = AutoOffsetReset.Earliest<br>        };<br><br>        _consumer = new ConsumerBuilder&lt;string, byte[]&gt;(config).Build();<br><br>        _consumer.Subscribe(topicName);<br><br>        Console.WriteLine(&quot;Waiting for movie events...&quot;);<br>        Console.WriteLine();<br>    }<br><br>    public void Consume(CancellationToken cancellationToken)<br>    {<br>        try<br>        {<br>            var consumeResult = _consumer.Consume(cancellationToken);<br><br>            var movie = JsonSerializer.Deserialize&lt;Movie&gt;(consumeResult.Message.Value);<br><br>            Console.WriteLine($&quot;Movie received:&quot;);<br>            Console.WriteLine($&quot;  Title: {movie?.Title}&quot;);<br>            Console.WriteLine($&quot;  Director: {movie?.Director}&quot;);<br>            Console.WriteLine($&quot;  Release Year: {movie?.ReleaseYear}&quot;);<br>            Console.WriteLine($&quot;  Genre: {movie?.Genre}&quot;);<br>            Console.WriteLine($&quot;  Rating: {movie?.Rating}&quot;);<br>            Console.WriteLine(new string(&#39;-&#39;, 50));<br><br>        }<br>        catch (ConsumeException ex)<br>        {<br>            Console.WriteLine($&quot;Error occurred while consuming message: {ex.Error.Reason}&quot;); <br>        }<br>    }<br><br>    public void Close()<br>    {<br>        _consumer.Close();<br>        Console.WriteLine(&quot;Kafka consumer closed.&quot;);<br>    }<br>}</pre><p>The consumer starts by creating a ConsumerConfig.</p><p><strong>BootstrapServers</strong> specifies the address of the Kafka broker that the consumer connects to.</p><p>Since we’re running Kafka locally using Docker, the broker is available on port <strong>9092</strong>.</p><p>Every Kafka consumer belongs to a <strong><em>Consumer Group</em></strong>.</p><p>Kafka uses the <strong>GroupId</strong> to keep track of which messages have already been processed by that group.</p><p>If multiple consumer instances share the same Group ID, Kafka distributes partitions among them, allowing the workload to be processed in parallel.</p><p>In our example, we’re using a single consumer, but it’s still good practice to assign it to a consumer group.</p><p><strong>AutoOffsetReset</strong> determines where the consumer starts reading if there is <strong><em>no previously committed offset</em></strong> for the consumer group.</p><p>The available options are:</p><ul><li>Earliest – start reading from the beginning of the topic.</li><li>Latest – start reading only newly published messages.</li></ul><p>For this article, we use <strong><em>Earliest</em></strong> so that the consumer can read the movie event that we published earlier.</p><p>Then the consumer subscribes to the movies topic and consumes the messages.</p><h4>ConsumerWorker</h4><p>Now create a Workers folder under the project and add ConsumerWorker with the following code:</p><pre>using KafkaConsumer.Services;<br>using Microsoft.Extensions.Hosting;<br><br>namespace KafkaConsumer.Workers;<br><br>public class ConsumerWorker(ConsumerService consumerService) : BackgroundService<br>{<br>    private readonly ConsumerService _consumerService = consumerService;<br><br>    protected override Task ExecuteAsync(CancellationToken stoppingToken)<br>    {<br>        return Task.Run(() =&gt;<br>        {<br>            try<br>            {<br>                while (!stoppingToken.IsCancellationRequested)<br>                {<br>                    _consumerService.Consume(stoppingToken);<br>                }<br>            }<br>            catch (OperationCanceledException)<br>            {<br>                Console.WriteLine(&quot;Kafka consumer service is stopping.&quot;);<br>            }<br>            finally<br>            {<br>                _consumerService.Close();<br>            }<br>        });<br>    }<br>}</pre><p>As you see, this is a background service that will run continuously and will consume the messages using the ConsumerService we created above.</p><h4>Program.cs</h4><p>As the final part of the coding, change the Program.cs with the following code:</p><pre>using KafkaConsumer.Configuration;<br>using KafkaConsumer.Services;<br>using KafkaConsumer.Workers;<br>using Microsoft.Extensions.Configuration;<br>using Microsoft.Extensions.DependencyInjection;<br>using Microsoft.Extensions.Hosting;<br><br>var builder = new ConfigurationBuilder()<br>    .AddJsonFile(&quot;appsettings.json&quot;)<br>    .AddEnvironmentVariables();<br><br>var configuration = builder.Build();<br><br>IHost host = Host.CreateDefaultBuilder(args)<br>    .ConfigureServices(services =&gt;<br>    {<br>        services.Configure&lt;KafkaConfiguration&gt;(configuration.GetSection(&quot;KafkaConfiguration&quot;));<br>        services.AddHostedService&lt;ConsumerWorker&gt;();<br>        services.AddSingleton&lt;ConsumerService&gt;();<br>    })<br>    .Build();<br><br>host.Run();</pre><p>Now if you run the <strong>KafkaConsumer </strong>project you will see the message sent to the topic before will be consumed as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/761/1*Xm5MceCLThHFiPeSijyUBg.png" /></figure><p>Congratulations! 😊 You’ve successfully published and read your first Kafka message.</p><p>And if you go to Kafka UI you can see the consumer as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hHF2-IwFS-TdxPLI11SXWw.png" /></figure><blockquote><strong>ConsumerLag = 0</strong> means that the consumer consumed all the messages in the topic.</blockquote><h3>Publishing and consuming events</h3><p>Now we will run both <strong>KafkaProducer </strong>and <strong>KafkaConsumer </strong>at the same time to see how the events are processed simultaneously.</p><p>First, lets add more events to the <strong>KafkaProducer </strong>so that we can notice they are streamed.</p><p>Change the Program.cs as below:</p><pre>using KafkaProducer.Configuration;<br>using KafkaProducer.Models;<br>using KafkaProducer.Services;<br>using Microsoft.Extensions.Configuration;<br><br>KafkaConfiguration kafkaConfig = GetConfiguration();<br><br>var producerService = new ProducerService&lt;Movie&gt;(kafkaConfig);<br><br>var topicName = &quot;movies&quot;;<br><br>var movies = CreateMovies();<br><br>foreach (var movie in movies)<br>{<br>    Console.WriteLine($&quot;Publishing &#39;{movie.Title}&#39;...&quot;);<br><br>    await producerService.ProduceMessageAsync(<br>        topicName,<br>        movie.Id.ToString(),<br>        movie);<br><br>    Console.WriteLine(&quot;Published successfully.&quot;);<br>    Console.WriteLine();<br><br>    await Task.Delay(1000);<br>}<br><br>Console.WriteLine(&quot;All movie events have been published.&quot;);<br><br>static KafkaConfiguration GetConfiguration()<br>{<br>    var builder = new ConfigurationBuilder()<br>        .AddJsonFile(&quot;appsettings.json&quot;)<br>        .AddEnvironmentVariables();<br><br>    var configuration = builder.Build();<br><br>    var kafkaConfig = new KafkaConfiguration();<br>    configuration.GetSection(&quot;KafkaConfiguration&quot;).Bind(kafkaConfig);<br>    return kafkaConfig;<br>}<br><br>static List&lt;Movie&gt; CreateMovies()<br>{<br>    return<br>    [<br>        new Movie<br>        {<br>            Id = 1,<br>            Title = &quot;Fight Club&quot;,<br>            Director = &quot;David Fincher&quot;,<br>            ReleaseYear = 1999,<br>            Genre = &quot;Drama&quot;,<br>            Rating = 8.8<br>        },<br><br>        new Movie<br>        {<br>            Id = 2,<br>            Title = &quot;Memento&quot;,<br>            Director = &quot;Christopher Nolan&quot;,<br>            ReleaseYear = 2000,<br>            Genre = &quot;Mystery&quot;,<br>            Rating = 8.4<br>        },<br><br>        new Movie<br>        {<br>            Id = 3,<br>            Title = &quot;The Sixth Sense&quot;,<br>            Director = &quot;M. Night Shyamalan&quot;,<br>            ReleaseYear = 1999,<br>            Genre = &quot;Psychological Thriller&quot;,<br>            Rating = 8.2<br>        },<br><br>        new Movie<br>        {<br>            Id = 4,<br>            Title = &quot;Eternal Sunshine of the Spotless Mind&quot;,<br>            Director = &quot;Michel Gondry&quot;,<br>            ReleaseYear = 2004,<br>            Genre = &quot;Science Fiction / Romance&quot;,<br>            Rating = 8.3<br>        },<br><br>        new Movie<br>        {<br>            Id = 5,<br>            Title = &quot;Children of Heaven&quot;,<br>            Director = &quot;Majid Majidi&quot;,<br>            ReleaseYear = 1997,<br>            Genre = &quot;Drama&quot;,<br>            Rating = 8.2<br>        }<br>    ];<br>}</pre><p>Now stop your <strong>KafkaConsumer</strong> project if it is still running.</p><p>To start both the <strong>KafkaProducer</strong> and <strong>KafkaConsumer</strong> projects simultaneously, right-click the solution in Visual Studio, select <strong><em>Properties</em></strong>, choose <strong><em>Configure startup projects</em></strong>, and set both projects to <strong><em>Start</em></strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/832/1*TjcVG_AoCRpqtC0RW7hlmg.png" /></figure><p>Now, if you click <strong>Ctrl+F5,</strong> you will see that the messages are sent and received as a stream:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dHfFqmJ6pvIct8B_m9EDRg.png" /></figure><p>If you go to Kafka UI and check the messages in the topic, you will see that messages are still there:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l9lvu2j0XVJCO-kPdmqrWw.png" /></figure><p>This proves that the messages are durable in Kafka topics, which was mentioned before.</p><p>You can find the source code in <a href="https://github.com/kilicars/KafkaDemo">this repository</a>.</p><p>That’s the end of the post. I hope you found this post helpful and easy to follow. If you have any questions and/or comments, you can share them in the responses section below.</p><p>And if you liked this post, please clap your hands 👏👏👏</p><p>Bye!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9b942926a34a" width="1" height="1" alt=""><hr><p><a href="https://medium.com/net-core/apache-kafka-with-net-from-core-concepts-to-a-working-application-9b942926a34a">Apache Kafka with .NET: From Core Concepts to a Working Application</a> was originally published in <a href="https://medium.com/net-core">.NET Core</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Refit in .NET]]></title>
            <link>https://medium.com/net-core/using-refit-in-net-0843bb199987?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/0843bb199987</guid>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[rest-api]]></category>
            <dc:creator><![CDATA[Sena Kılıçarslan]]></dc:creator>
            <pubDate>Mon, 01 Jan 2024 09:13:17 GMT</pubDate>
            <atom:updated>2024-01-02T16:57:32.393Z</atom:updated>
            <content:encoded><![CDATA[<h3><strong>Using Refit in .NET</strong></h3><p>In this post, I will demonstrate how to use Refit library in ASP.NET Core.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Uiw9BUPBaw0AeRXw-7gy6w.jpeg" /></figure><p>We will build an ASP.NET Core Web API that will make external API calls to TMDB API and we will use Refit library while calling this external API.</p><p>Our API will have endpoints for the following purposes:</p><ul><li>Search an actor/actress by name</li><li>Get the movies of an actor/actress</li><li>Add a rating to a movie</li><li>Delete a rating from a movie</li></ul><p>At the end, the API will look like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wE--KuUSQqXOTAWwxKA_VA.png" /></figure><p>The sections of this post will be as follows:</p><ul><li>What is Refit?</li><li>Demo Application<br>- “Search an actor/actress by name” endpoint (GET)<br>- “Get the movies of an actor/actress” endpoint (GET)<br>- “Add a rating to a movie” endpoint (POST)<br>- “Delete a rating from a movie” endpoint (DELETE)<br>- ApiResponse&lt;T&gt; wrapper class</li></ul><h3>What is Refit?</h3><blockquote>Refit is an automatic type-safe REST library for .NET. It is heavily inspired by Square’s Retrofit library, Refit turns your REST API into a live interface.</blockquote><p>Refit reduces the amount of code required to call APIs by eliminating the need to manually construct HTTP requests and handle responses.</p><p>It provides automatic serialization and deserialization of data.</p><p>Refit currently supports the following platforms and any .NET Standard 2.0 target:</p><ul><li>UWP</li><li>Xamarin</li><li>Desktop .NET 4.6.1</li><li>.NET 5 / .NET Core</li><li>Blazor</li><li>Uno Platform</li></ul><p>The current version of Refit is 7.0.0 and you can see the latest version <a href="https://www.nuget.org/packages/Refit/">here</a>.</p><blockquote>Refit 6 requires Visual Studio 16.8 or higher, or the .NET SDK 5.0.100 or higher. It can target any .NET Standard 2.0 platform.</blockquote><blockquote>Additionally, Refit 6 makes <a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-overview">System.Text.Json</a> the default JSON serializer.</blockquote><p>You can find the GitHub repository of Refit <a href="https://github.com/reactiveui/refit">here</a>.</p><p>We will dive into the details of using Refit in our demo project below.</p><p>If you are ready, let’s start :)</p><h3>Demo Application</h3><p>The API will make external API calls to the <a href="https://www.themoviedb.org/">TMDb</a> API. If you want to follow along while implementing the application, please go <a href="https://developer.themoviedb.org/docs/getting-started">here</a> and get your <strong><em>API Read Access Token</em></strong> to be able to call this API.</p><p>Now, let’s start with creating the project.</p><p>I will use Visual Studio 2022 and .NET 8 for the demo application.</p><p>Open VS 2022 and select <em>Create a new project</em> and then select <em>ASP.NET Core Web API</em>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2ppYMcNPR3mhxFqXPtXguA.png" /></figure><p>Please make sure that <em>Enable OpenAPI support</em> is checked so that we can use Swagger to test our endpoints.</p><p><em>You can delete the default class and the controller (</em><em>WeatherForecast.cs and </em><em>WeatherForecastController.cs) as we will not use those.</em></p><p>Add the Refit and Refit.HttpClientFactory Nuget packages to the project:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Lm7me4AClTb5mkRc_9msDA.png" /></figure><h4>“Search an actor/actress by name” endpoint (GET)</h4><p>Now, we will add the implementation for our first endpoint.</p><p>First, add Models folder in the project and add Actor and ActorList classes under it:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/417ef755c570475246bac3a089eeb6d7/href">https://medium.com/media/417ef755c570475246bac3a089eeb6d7/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/0cae57ab36dd80d1857b914e91738d58/href">https://medium.com/media/0cae57ab36dd80d1857b914e91738d58/href</a></iframe><p>Next, add Services folder to the project and create ITmdbApi interface with the following code under it:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lnhuVDc4h_ZyyklSljOqwA.png" /><figcaption>ITmdbApi.cs</figcaption></figure><p>Now, let’s examine specific features related to Refit here.</p><blockquote>Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: <strong>Get, Post, Put, Delete,</strong> <strong>Patch </strong>and <strong>Head</strong>. The relative URL of the resource is specified in the annotation.</blockquote><blockquote>You can also specify query parameters in the URL. A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.</blockquote><p>We use the GET attribute for our method as follows:</p><pre> [Get(&quot;/search/person?query={name}&quot;)]<br> Task&lt;ActorList&gt; GetActors(string name);</pre><p>The complete path to query persons in TMDB API is:</p><pre>https://api.themoviedb.org/3/search/person?query={name}</pre><p>Since the part https://api.themoviedb.org/3 is common across all endpoints, this part will be defined as base URL later in Program.cs</p><p>The relative URL /search/person?query={name} is specified in the annotation. And {name} is matched with the parameter name in the method definition.</p><blockquote>If the name of your parameter doesn’t match the name in the URL path, use the AliasAs attribute.</blockquote><pre>[Get(&quot;/search/person?query={name}&quot;)]<br>Task&lt;ActorList&gt; GetActors([AliasAs(&quot;name&quot;)] string actorName);</pre><blockquote>You can set one or more static request headers for a request applying a Headers attribute to the method:</blockquote><pre>[Headers(&quot;accept: application/json&quot;,<br>         &quot;Authorization: Bearer&quot;)]</pre><p>As we will use token authentication with TMDB API, we need to add Authorization: Bearer header in the interface. (<em>The rest of the token authentication will be done in </em><em>Program.cs later)</em></p><blockquote>Note that in Refit unlike in Retrofit, there is no option for a synchronous network request — all requests must be async, either via Task or via IObservable.</blockquote><p>So we use Task&lt;ActorList&gt; in the method definition.</p><p>Now, we will add the necessary code to Program.cs</p><blockquote>Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:</blockquote><pre>builder.Services<br>    .AddRefitClient&lt;ITmdbApi&gt;(refitSettings)<br>    .ConfigureHttpClient(c =&gt; c.BaseAddress = new Uri(&quot;https://api.themoviedb.org/3&quot;));</pre><p>We configure the Refit client with the base URL <a href="https://api.themoviedb.org/3">https://api.themoviedb.org/3</a> here.</p><p>Notice that we are passing refitSettings parameter to the AddRefitClient</p><blockquote>RefitSettings defines various parameters on how Refit should work.</blockquote><p>We can use RefitSettings for many purposes to customize our client. In our case, we will use it to pass our <strong><em>API Read Access Token </em></strong>to the client:</p><pre>var authToken = &quot;***WRITE YOUR API READ ACCESS TOKEN HERE***&quot;;<br>var refitSettings = new RefitSettings()<br>{<br>    AuthorizationHeaderValueGetter = (rq, ct) =&gt; Task.FromResult(authToken),<br>};</pre><p>So, our Program.cs will look like this after those additions:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yKNzQrh4GHqEfEDYNZmbzw.png" /><figcaption>Program.cs</figcaption></figure><p>For TMDB API, access token is static. But if you need to generate a token dynamically, you can do it in a function, say GetTokenAsync , and then you can use it like below:</p><pre>var refitSettings = new RefitSettings()<br>{<br>    AuthorizationHeaderValueGetter = (rq, ct) =&gt; GetTokenAsync(),<br>};</pre><p>Finally, we will add an API Controller to our project and inject ITmdbApi into it.</p><p>Add a new folder called Controllers to the project and then add an empty API Controller named MovieDbController to it and add the following code:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K3u_YmwirmnhRLfmTpAJew.png" /><figcaption>MovieDbController.cs</figcaption></figure><p>Now, we can test our first endpoint.</p><p>Run the project and click <strong><em>Try It Out</em></strong> and then enter the name and/or surname of the actor/actress you want to see and click <strong><em>Execute</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ne1LuEUrr-nstY1hi6mxbw.png" /></figure><p>Then we see the results as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mx1VSy9z4YWEcqvrL7erWw.png" /></figure><p>Please note the id of the person you want to use as we will use that in the next endpoint.</p><h4>“Get the movies of an actor/actress” endpoint (GET)</h4><p>Now, we will implement the endpoint to get the movies of an actor/actress.</p><p>First, add the Movie and MovieList classes under the Models folder:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2d1ad97ffb5263166b7a4ef4625cd9ff/href">https://medium.com/media/2d1ad97ffb5263166b7a4ef4625cd9ff/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/91aa89b1513dee7052911ff91d4e2fc9/href">https://medium.com/media/91aa89b1513dee7052911ff91d4e2fc9/href</a></iframe><p>Next, add the following code to ITmdbApi :</p><pre> [Get(&quot;/person/{actorId}/movie_credits?language=en-US&quot;)]<br> Task&lt;MovieList&gt; GetMovies(int actorId);</pre><blockquote>The comparison between parameter name and URL parameter is <em>not</em> case-sensitive, so it will work correctly if you name your parameter as actorid</blockquote><p>Finally, add the following code to MovieDbController :</p><pre>[HttpGet(&quot;actors/{actorId}/movies&quot;)]<br>public async Task&lt;MovieList&gt; GetMovies(int actorId)<br>{<br>    var response = await _tmdbApi.GetMovies(actorId);<br>    return response;<br>}</pre><p>Now, we can test the endpoint using the id of the actor/actress that we found in the previous endpoint:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k5SzfEi7R2tmW1RIOzRHFA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*umSXvjlSSdAZZDRVQZH9wQ.png" /></figure><p>Please note the Id of the movie you want to use as we will use that in the next endpoints to add/delete rating.</p><h4>“Add a rating to a movie” endpoint (POST)</h4><p>Now, we will add a rating to our favorite movie :)</p><p>First, add the Rating and ResponseBody classes under the Models folder:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/71e9b558be08407a001ed1a9cb8c793a/href">https://medium.com/media/71e9b558be08407a001ed1a9cb8c793a/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/52ea75cb65a31c674f2ad9717fddf968/href">https://medium.com/media/52ea75cb65a31c674f2ad9717fddf968/href</a></iframe><p>Next, add the following code to ITmdbApi :</p><pre>   [Headers(&quot;Content-Type: application/json;charset=utf-8&quot;)]<br>   [Post(&quot;/movie/{movieId}/rating&quot;)]<br>   Task&lt;ResponseBody&gt; AddRating(int movieId, [Body] Rating rating);</pre><p>Now, let’s examine what we added here in detail:</p><p>We use Post attribute in the data annotation as this is a POST endpoint.</p><blockquote>One of the parameters in your method can be used as the body, by using the Body attribute</blockquote><p>We use Body attribute here to pass the Rating object.</p><p>And we include a new Headers attribute for the Content-Type header. Please notice that we already have a Header attribute for the <em>interface</em> and now we added a new one for the <em>method</em>.</p><p>There can be three ways to define a Headers attribute and below is the priority for those when they are defined for the <strong><em>same</em></strong> header:</p><blockquote>Headers attribute on the interface <em>(lowest priority)</em></blockquote><blockquote>Headers attribute on the method</blockquote><blockquote>Header attribute or HeaderCollection on a method parameter <em>(highest priority)</em></blockquote><p>This redefining behavior only applies to headers <em>with the same name</em>. Headers with different names are not replaced. So, all headers will be included for our code.</p><p>Finally, add the following code to the MovieDbController :</p><pre>[HttpPost(&quot;movies/{movieId}/rating&quot;)]<br>public async Task&lt;ResponseBody&gt; AddRating(int movieId, [FromBody] Rating rating)<br>{<br>   var response = await _tmdbApi.AddRating(movieId, rating);<br>   return response;<br>}</pre><p>Now, we can add our rating to the movie:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T_DY3ld1Ohp_Vyn7jRJPBw.png" /></figure><p>And we get the Success message when we execute it:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2u0IWHwjGzrV_sUVldRbvA.png" /></figure><p>Also, we can check that the rating is added to our TMDB account:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*59l3DE9c8uIcCiiQw23dyw.png" /></figure><h4>“Delete a rating from a movie” endpoint (DELETE)</h4><p>Now, we will implement the endpoint to delete a rating.</p><p>Add the following code to ITmdbApi :</p><pre>  [Delete(&quot;/movie/{movieId}/rating&quot;)]<br>  Task&lt;ResponseBody&gt; DeleteRating(int movieId);</pre><p>Then, add the following code to IMovieDbController :</p><pre> [HttpDelete(&quot;movies/{movieId}/rating&quot;)]<br> public async Task&lt;ResponseBody&gt; DeleteRating(int movieId)<br> {<br>    var response = await _tmdbApi.DeleteRating(movieId);<br>    return response;<br> }</pre><p>Now, we can use the endpoint after running the project:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VGS-Tat0oOxBYaB7uMXk_A.png" /></figure><p>If we check our account on the TMDB site then we will see the rating is removed:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CRXs7xq2mUbREdNwRkvyBA.png" /></figure><h4>ApiResponse&lt;T&gt; wrapper class</h4><blockquote>There is also a generic wrapper class called ApiResponse&lt;T&gt; that can be used as a return type. Using this class as a return type allows you to retrieve not just the content as an object, but also any metadata associated with the request/response. This includes information such as response headers, the http status code and reason phrase (e.g. 404 Not Found), the response version, the original request message that was sent and in the case of an error, an ApiException object containing details of the error.</blockquote><p>Lastly, I want to demonstrate how to use ApiResponse&lt;T&gt; wrapper class as I find it very helpful.</p><p>We will change our <strong><em>Search an actor/actress by name</em></strong> GET endpoint.</p><p>First, change the GetActors method in the ITmdbApi as follows:</p><pre> [Get(&quot;/search/person?query={name}&quot;)]<br> Task&lt;ApiResponse&lt;ActorList&gt;&gt; GetActors(string name);</pre><p>Then, change the GetActors method in the MovieDbController as below:</p><pre> [HttpGet(&quot;actors/&quot;)]<br> public async Task&lt;ActorList&gt; GetActors([FromQuery][Required] string name)<br> {<br>    var result = new ActorList();<br>    var response = await _tmdbApi.GetActors(name);<br>    if (response.IsSuccessStatusCode)<br>    {<br>        result = response.Content;<br>    }<br>    else<br>    {<br>        // log $&quot;{response.Error}-{response.Error.Content}&quot;;<br>    }<br>    return result;<br>  }</pre><p>Now, you can run the project and see it still functions properly. In this way, you have more information on the request/response that you can use for controls, exception handling, logging, etc.</p><p>You can find the source code on this <a href="https://github.com/kilicars/AspNetCoreRefitDemo">GitHub repository</a>.</p><p>That’s the end of the post. I hope you found this post helpful and easy to follow. If you have any questions and/or comments, you can share them in the responses section below.</p><p>And if you liked this post, please clap your hands 👏👏👏</p><p>Bye!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0843bb199987" width="1" height="1" alt=""><hr><p><a href="https://medium.com/net-core/using-refit-in-net-0843bb199987">Using Refit in .NET</a> was originally published in <a href="https://medium.com/net-core">.NET Core</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Build a RESTful Web API with ASP.NET Core 6]]></title>
            <link>https://medium.com/net-core/build-a-restful-web-api-with-asp-net-core-6-30747197e229?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/30747197e229</guid>
            <category><![CDATA[rest-api]]></category>
            <category><![CDATA[dotnet-core]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[webapi]]></category>
            <category><![CDATA[ef-core]]></category>
            <dc:creator><![CDATA[Sena Kılıçarslan]]></dc:creator>
            <pubDate>Wed, 29 Jun 2022 17:00:09 GMT</pubDate>
            <atom:updated>2023-09-16T09:36:03.505Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post, I will demonstrate how to build a RESTful Web API using ASP.NET Core 6.0 and Entity Framework Core.</p><p><em>.NET 6 is the </em><a href="https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core"><em>latest LTS (Long Term Support) release</em></a><em> currently and will be supported until November 12, 2024.</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t3S6idTOhL9rn6diPuWsZA.jpeg" /></figure><p>This API will manage movie records stored in a relational database (SQL Server) as described in the table below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kxwi-n_nqJdZVBCzMMKpeg.png" /></figure><p>The sections of this post will be as follows:</p><ul><li>What is REST?</li><li>Creating a Web API Project</li><li>Adding a Model</li><li>Adding a Database Context</li><li>Creating Database with Migrations</li><li>Creating API Controller and Methods</li></ul><p>We need the following tools installed on our computer:</p><ul><li><a href="https://visualstudio.microsoft.com/vs/#download">Visual Studio 2022</a></li><li><a href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0">.NET 6.0 SDK</a></li><li><a href="https://www.microsoft.com/en-us/sql-server/sql-server-editions-express">Microsoft SQL Server Express</a></li></ul><p>If you are ready, let’s get started.</p><h3>What is REST?</h3><p><strong>RESTful</strong> APIs conform to the REST architectural style.</p><p><strong>REST</strong>, or <strong>RE</strong>presentational <strong>S</strong>tate <strong>T</strong>ransfer, is an architectural style for providing standards between computer systems on the web, making it easier for systems to communicate with each other.</p><p>REST relies on <strong>client-server</strong> relationship. This essentially means that client application and server application must be able to evolve separately without any dependency on each other.</p><p>REST is <strong>stateless</strong>. That means the communication between the client and the server always contains all the information needed to perform the request. There is no session state in the server, it is kept entirely on the client’s side.</p><p>REST provides a <strong>uniform interface</strong> between components. Resources expose directory structure-like URIs.</p><p>REST is not strictly related to HTTP, but it is most commonly associated with it. There are four basic HTTP verbs we use in requests to interact with resources in a REST system:</p><ul><li><strong>GET</strong> — retrieve a specific resource (by id) or a collection of resources</li><li><strong>POST</strong> — create a new resource</li><li><strong>PUT</strong> — update a specific resource (by id)</li><li><strong>DELETE</strong> — remove a specific resource by id</li></ul><p>In a REST system, representations transfer <strong>JSON</strong> or <strong>XML</strong> to represent data objects and attributes.</p><p>REST has had such a large impact on the Web that it has mostly displaced SOAP-based interface design because it’s a considerably simpler style to use.</p><p>Now that we have made a quick review of REST, we can continue with the implementation.</p><h3>Creating a Web API Project</h3><p>Open Visual Studio 2022 and select <strong><em>Create a new project</em></strong> and then select <strong><em>ASP.NET Core Web API</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*s_pLHCGAfMiRzWZD79bEZw.png" /></figure><p>and give a name to your project in the following screen and then click <strong><em>Next</em></strong>.</p><p>In the next screen, select <strong>.NET 6.0</strong> as the framework and click <strong><em>Create</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*DO0qKH6YOyrxccLc03WXdA.png" /></figure><p>At this point we have a starter project as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/432/1*_qAsTLOc13dDaJxQdtcowQ.png" /><figcaption>Default project structure</figcaption></figure><p>In the <strong><em>Program.cs</em></strong> we see that Swagger support is added automatically to our project:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bUsxafvRP9Ss63jJeYfWqg.png" /><figcaption>Initially created Program.cs</figcaption></figure><p>And also <strong><em>Swashbuckle.AspNetCore</em></strong> NuGet package is added as a dependency.</p><p><em>For more information on Swagger, see </em><a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-6.0"><em>ASP.NET Core web API documentation with Swagger / OpenAPI</em></a><em>.</em></p><p>Now, let’s run (Ctlr+F5) the project to see the default output. When the browser opens and the Swagger UI is shown, select the <strong>GET</strong> method in the <strong><em>WeatherForecast</em></strong> part and then select <strong><em>Try It Out</em></strong> and <strong><em>Execute</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*D3kXKvCARH34bbIup3soZw.png" /><figcaption>Swagger UI in browser</figcaption></figure><p>Also, you can use the curl URL shown in the Swagger UI for this method and see the result of the URL in the browser:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/713/1*YvX3TaPbMl8lzKDLV5va-w.png" /></figure><p>When we run the application, the default URL comes from the <strong><em>launchSettings.json</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/920/1*aNU4mtB56hL1WaTi28Bqig.png" /><figcaption>launchSettings.json</figcaption></figure><p>And the result values come from the GET method of the <strong><em>WeatherForecastController</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yr5DYKSlTD5prU2DR7TnpA.png" /><figcaption>WeatherForecatController.cs</figcaption></figure><p>As you see, values here are hard coded and randomness is added to generate different values.</p><p>In our Web API, we will create our own records in an SQL server database and will be able to view, update and delete them through REST API endpoints.</p><h3>Adding a Model</h3><p>Now, we will implement our data model class.</p><p>In <strong><em>Solution Explorer</em></strong>, right-click the project. Select <strong><em>Add</em></strong><em> -&gt; </em><strong><em>New Folder </em></strong>and name the folder <strong><em>Models</em></strong>.</p><p>Then right-click the <strong><em>Models </em></strong>folder and select <strong><em>Add-&gt;Class</em></strong><em>. </em>Name the class <strong><em>Movie.cs</em></strong><em> </em>and click <strong><em>Add</em></strong>.</p><p>Next, add the following properties to the class:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/717/1*aZ5rWNU5l8lGfqXLos5a8g.png" /><figcaption>Movie.cs</figcaption></figure><p>The Id field is required by the database for the primary key.</p><h4>Entity Framework Core</h4><p>We will use our model with <a href="https://docs.microsoft.com/en-us/ef/core"><strong><em>Entity Framework Core</em></strong></a><strong><em> (EF Core)</em></strong> to work with a database.</p><blockquote><em>EF Core is an object-relational mapping (ORM) framework that simplifies the data access code. Model classes don’t have any dependency on EF Core. They just define the properties of the data that will be stored in the database.</em></blockquote><p>In this post, we will write the model classes first and <strong><em>EF Core</em></strong> will create the database. This is called <strong><em>Code First Approach</em></strong>.</p><p>Let’s add the EF Core NuGet packages to the project. Right-click on the project and select <strong><em>Manage NuGet Packages…</em></strong> and then install the following packages:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B4cZuSBRfNUdJBUXf1lWwA.png" /><figcaption>NuGet Package Manager</figcaption></figure><h3>Adding a Database Context</h3><p>The <em>database context</em> is the main class that coordinates Entity Framework functionality for a data model. This class is created by deriving from the <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbcontext">Microsoft.EntityFrameworkCore.DbContext</a> class.</p><p>Now, right-click the Models folder and select <strong><em>Add -&gt;Class</em></strong>. Name the class <strong><em>MovieContext</em></strong> and click <strong><em>Add</em></strong>. Then add the following code to the class:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0Zw7RjuVEaSYe8uO7vzhOw.png" /><figcaption>MovieContext.cs</figcaption></figure><p>UPDATE (26.08.2023): You need to addpublic keyword to the above line as below:</p><pre>public DbSet&lt;Movie&gt; Movies { get; set; } = null!;</pre><p>The preceding code creates a DbSet&lt;Movie&gt; property for the <strong><em>entity set</em></strong>.</p><blockquote><em>In Entity Framework terminology, an entity set typically corresponds to a database table and an entity corresponds to a row in the table.</em></blockquote><p>The name of the connection string is passed into the context by calling a method on a DbContextOptions object. For local development, the <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0">ASP.NET Core configuration system</a> reads the connection string from the <strong><em>appsettings.json</em></strong> file.</p><p>We need to add our connection string to the <strong><em>appsettings.json</em></strong>. I will use the local SQL server instance in my machine and we can define the connection string as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Kc6Mu3KAt8-yk6eS1mj4zg.png" /><figcaption>appsettings.json</figcaption></figure><p><em>You can change the database name if you want.</em></p><h4>Dependency Injection</h4><blockquote>ASP.NET Core is built with <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-6.0">Dependency Injection (DI)</a>. Services (such as the EF Core DB context) are registered with DI during application startup. Components that require these services are provided with these services via constructor parameters.</blockquote><p>Now, we will register our database context to the built-in IOC container. Add the following code to <strong><em>Program.cs</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1p3FBtDlWS4z4zMjg-rJSw.png" /><figcaption>Program.cs</figcaption></figure><h3>Creating Database with Migrations</h3><p>Now, we will create the database using the EF Core <a href="https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=vs">Migrations </a>feature.</p><blockquote><em>Migrations lets us create a database that matches our data model and update the database schema when our data model changes.</em></blockquote><p>First, we will add an initial Migration.</p><p>Open <strong><em>Tools -&gt; NuGet Package Manager &gt; Package Manager Console(PMC)</em></strong> and run the following command in the PMC:</p><p>Add-Migration Initial</p><p>The Add-Migration command generates code to create the initial database schema which is based on the model specified in the <strong><em>MovieContext </em></strong>class. The <strong><em>Initial </em></strong>argument is the migration name and any name can be used.</p><p>After running the command, a migration file is created under the <strong><em>Migrations</em></strong> folder:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NuMywn37u88FnHbNE274sg.png" /><figcaption>Initial migrations file</figcaption></figure><p>As the next step, run the following command in the PMC:</p><p>Update-Database</p><p>The Update-Database command runs the Up method in the <strong><em>Migrations/{time-stamp}_Initial.cs</em></strong> file, which creates the database.</p><p>Now, we will check the database created. Open <strong><em>View -&gt; SQL Server Object Explorer.</em></strong></p><p>You will see the newly created database as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/886/1*ONVP6spuGOiR--9dux2xUg.png" /><figcaption>SQL Server Object Explorer</figcaption></figure><p>As you see, the <strong><em>Movie </em></strong>table and the <strong><em>Migrations History </em></strong>table are created automatically. Then a record is inserted into the migration history table to show the executed migrations on the database.</p><h3>Creating API Controller and Methods</h3><p>In this section, we will create the Movies API Controller and add the methods to it, and also will test those methods.</p><p>Let’s add the controller first. Right-click on the <strong><em>Controller</em></strong> folder and select <strong><em>Add -&gt; Controller..</em></strong> and then select <strong><em>API Controller - Empty</em></strong> as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BWmP98XSf663LrCMy16L4Q.png" /></figure><p>Click <strong><em>Add</em></strong> and give a name to your controller on the next screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_KVoEgPmNh18J-B4rT6iDw.png" /></figure><p><strong><em>MoviesController</em></strong> is created as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1011/1*jsMCup-P6wtWasv8VbE8UA.png" /><figcaption>MoviesController.cs</figcaption></figure><p>As you see, the class is decorated with the [<a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.apicontrollerattribute">ApiController]</a> attribute. This attribute indicates that the controller responds to web API requests.</p><p><strong><em>MoviesController </em></strong>class inherits from <strong><em>ControllerBase.</em></strong></p><p>Next, we will inject the database context mentioned in the previous section through the constructor of the controller. Add the following code:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qoxv4MbIfZcy3oWguXV2_w.png" /><figcaption>MoviesController.cs</figcaption></figure><p>Now, we will add <a href="https://wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> (create, read, update, and delete) action methods to the controller. Let’s start with the GET methods.</p><h4>GET Method</h4><p>Add the following code to the MoviesController:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HvCzWg-j5dX1R0-FTvCBOg.png" /><figcaption>GET methods</figcaption></figure><p>GetMovies method returns all the movies and GetMovie(int id) method returns the movie having the <em>Id</em> given as input. They are decorated with the <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.httpgetattribute">[HttpGet]</a> attribute which denotes that a method responds to an HTTP GET request.</p><p>These methods implement two GET endpoints:</p><ul><li>GET /api/Movies</li><li>GET /api/Movies/{id}</li></ul><p>We can test the app by calling the two endpoints from a browser as follows:</p><ul><li><a href="https://localhost:{port}/api/movies">https://localhost:{port}/api/movies</a></li><li><a href="https://localhost:{port}/api/movies/{id}">https://localhost:{port}/api/movies/{id}</a></li></ul><p>The return type of the GetMovie methods is <a href="https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-6.0"><strong>ActionResult&lt;T&gt; type</strong></a>. ASP.NET Core automatically serializes the object to <a href="https://www.json.org/"><strong>JSON</strong></a><strong> </strong>and writes the JSON into the body of the response message. The response code for this return type is <strong>200</strong>, assuming there are no unhandled exceptions. Unhandled exceptions are translated into <strong>5xx</strong> errors.</p><p><strong>Routing and URL Paths</strong></p><p>The URL path for each method is constructed as follows:</p><p>Start with the template string in the controller’s Route attribute (Route(&quot;api/[controller]&quot;)). Then replace [controller] with the name of the controller, which by convention is the controller class name minus the <em>Controller</em> suffix. For this sample, the controller class name is MoviesController, so the controller name is movies.</p><p><em>ASP.NET Core </em><a href="https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-6.0"><em>routing</em></a><em> is case insensitive.</em></p><p><strong>Testing the GetMovie Method</strong></p><p>Now we will test these endpoints. Before that, let’s insert some movie records into our table.</p><p>Go to the <strong><em>SQL Server Object Explorer</em></strong> and right-click the <strong><em>Movies</em></strong> table and select <strong><em>View Data</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/475/1*gagvBdyvQchEYoD3zThqAw.png" /></figure><p>Then add some movie records manually to the table:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/687/1*F0o8dTowvGscs_crdAPSFw.png" /></figure><p>We do not need to add data for the <em>Id</em> column as SQL Server automatically handles this for us.</p><p>Now, we can test the GET endpoints. Start (Ctlr+F5) the application:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y9oHEKuKcMkW0XcDbFzNLg.png" /></figure><p>Select the first GET method and click <strong><em>Try it out -&gt; Execute</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WorEgURSN-U435-zG_xOYA.png" /></figure><p>This shows all of the movies in the application.</p><p>Next, click the second GET method and click <strong><em>Try it out</em></strong> and enter one of the <em>Id</em>s above in the <strong><em>id</em></strong> field and click <strong><em>Execute</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cVaJNVDoMtqXFnKzEIIjZA.png" /></figure><p>If no item matches the requested Id, the method returns a <strong>404 </strong><a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.notfound"><strong>NotFound</strong></a> error code.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j06wI01ufWTkdQE42nPFig.png" /></figure><h4>POST Method</h4><p>Add the following code to the MoviesController:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/987/1*0BXA7Y4PLs8lU0PqkEkFlA.png" /><figcaption>POST method</figcaption></figure><p>PostMovie method creates a movie record in the database. The preceding code is an HTTP POST method, as indicated by the <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.httppostattribute">[HttpPost]</a> attribute. The method gets the value of the movie record from the body of the HTTP request.</p><p>The CreatedAtAction method:</p><ul><li>Returns an <strong>HTTP</strong> <strong>201</strong> status code, if successful. HTTP 201 is the standard response for an HTTP POST method that creates a new resource on the server.</li><li>Adds a Location header to the response. The Location header specifies the URI of the newly created movie record.</li><li>References the GetMovie action to create the Location header&#39;s URI.</li></ul><p><strong>Testing the PostMovie Method</strong></p><p>Start the application and then select the POST method in the <strong><em>Movies</em></strong> section.</p><p>Click <strong><em>Try it out </em></strong>and enter the movie information that you want to add in the request body:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FOo4Ph48WdgFc-Kb-QyRJw.png" /></figure><p>and click <strong><em>Execute</em></strong>.</p><p>Response status code is <strong>201 (Created)</strong> and a location header is added to the response as seen below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1TzxDXTED_t-DvIFUnt4Mg.png" /></figure><p>We can paste this location URL in the browser and see the response there too:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/549/1*xrcZ3F3MyQ9ZZ7twf-qTFA.png" /></figure><p>Also, we can check this record from the <strong><em>Movies </em></strong>table in our local database:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/670/1*OsQdY3LkG26v97SIsZY2eA.png" /></figure><h4>PUT Method</h4><p>Add the following code to the MoviesController:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i-VMbzX8LUNixOP8QvFvWw.png" /><figcaption>PUT method</figcaption></figure><p>PutMovie method updates the movie record with the given <em>Id</em> in the database.<strong> </strong>The preceding code is an HTTP PUT method, as indicated by the [<a href="https://docs.microsoft.com/en-gb/dotnet/api/microsoft.aspnetcore.mvc.httpputattribute?view=aspnetcore-6.0">HttpPut</a>] attribute. The method gets the value of the movie record from the body of the HTTP request. You need to supply the <em>Id</em> both in the request URL and the body and they have to match. According to the HTTP specification, a PUT request requires the client to send the entire updated entity, not just the changes.</p><p>The response is <a href="https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html"><strong>204 (No Content)</strong></a> if the operation is successful.</p><p><strong>Testing the PutMovie Method</strong></p><p>Start the application and then select the PUT method in the <strong><em>Movies</em></strong> section.</p><p>Click <strong><em>Try it out </em></strong>and enter the movie information that you want to update in the request body and the <em>Id</em> of the movie in the <strong><em>id</em></strong> field:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U3dtYOo3ZoJ7ddcajlJSyw.png" /></figure><p>and then click <strong><em>Execute</em></strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*B5rgFXY1dYeYIzzzrlnTIg.png" /></figure><p>We can check the updated state of the movie from GET method with Id in the Swagger UI or directly from the browser as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fqQoBqYYM8M76QQVmGWp8Q.png" /></figure><p>We can see the updated info in the database as well:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*elkzmkqH7DguvmFyeCm7MQ.png" /><figcaption>Movies table</figcaption></figure><p>If we try to update a record that does not exist in the database we get <strong>404 Not Found</strong> error:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*khTJ2pcp3-OGv1HLr-4EJQ.png" /></figure><h4>DELETE Method</h4><p>Add the following code to the MoviesController:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mGbqw3y3VVwCqg1esJnX5Q.png" /></figure><p>DeleteMovie method deletes the movie record with the given <em>Id</em> in the database.<strong> </strong>The preceding code is an HTTP DELETE method, as indicated by the [<a href="https://docs.microsoft.com/en-gb/dotnet/api/microsoft.aspnetcore.mvc.httpdeleteattribute?view=aspnetcore-6.0">HttpDelete</a>] attribute. This method expects <em>Id</em> in the URL to identify the movie record we want to delete.</p><p><strong>Testing the DeleteMovie Method</strong></p><p>Start the application and then select the DELETE method in the <strong><em>Movies</em></strong> section.</p><p>Click <strong><em>Try it out </em></strong>and enter the <em>Id</em> of the movie you want to delete in the <strong><em>id</em></strong> field:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RUS1PK8mxRbry4eaEYl6gg.png" /></figure><p>and then click <strong><em>Execute.</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h62Db8q81SKOiydoCorgzA.png" /></figure><p>We do not need to supply a request body as you might have noticed. The response status is <strong>204 No Content.</strong></p><p>If we try to get this movie record using the browser we get <strong>404 Not Found</strong> error as expected:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fRptMjZYzUiY6-nmXc42ew.png" /></figure><p>We can check as well from the database that the record is deleted:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*oAilKgA-3s6cLd6BuloFNQ.png" /></figure><p>This is where this post ends. You can find the full project in this <a href="https://github.com/kilicars/AspNetCoreWebApi6">GitHub repository</a>.</p><p>I hope you found this post helpful and easy to follow. Please let me know if you have any corrections and/or questions in the comments below.</p><p>And if you liked this post, please clap your hands 👏👏👏</p><p>Bye!</p><p><strong>References</strong></p><p><a href="https://www.codecademy.com/articles/what-is-rest">https://www.codecademy.com/articles/what-is-rest</a></p><p><a href="https://developer.ibm.com/articles/ws-restful/">https://developer.ibm.com/articles/ws-restful/</a></p><p><a href="https://ninenines.eu/docs/en/cowboy/2.6/guide/rest_principles/">https://ninenines.eu/docs/en/cowboy/2.6/guide/rest_principles/</a></p><p><a href="https://restfulapi.net/rest-architectural-constraints/">https://restfulapi.net/rest-architectural-constraints/</a></p><p><a href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0">https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-6.0</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=30747197e229" width="1" height="1" alt=""><hr><p><a href="https://medium.com/net-core/build-a-restful-web-api-with-asp-net-core-6-30747197e229">Build a RESTful Web API with ASP.NET Core 6</a> was originally published in <a href="https://medium.com/net-core">.NET Core</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to Avoid Exponential Growth of Classes by Applying Bridge Pattern in C#]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/net-core/how-to-avoid-exponential-growth-of-classes-by-applying-bridge-pattern-in-c-2d437e203f82?source=rss----9864284e8ea3---4"><img src="https://cdn-images-1.medium.com/max/2600/1*ETl3auHD4H57i2Q8NUPZOw.jpeg" width="7900"></a></p><p class="medium-feed-snippet">I wrongly designed my code which could lead to the multiplication classes. Then I was introduced to Bridge Pattern and fix my code design.</p><p class="medium-feed-link"><a href="https://medium.com/net-core/how-to-avoid-exponential-growth-of-classes-by-applying-bridge-pattern-in-c-2d437e203f82?source=rss----9864284e8ea3---4">Continue reading on .NET Core »</a></p></div>]]></description>
            <link>https://medium.com/net-core/how-to-avoid-exponential-growth-of-classes-by-applying-bridge-pattern-in-c-2d437e203f82?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/2d437e203f82</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[software-architecture]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[design-patterns]]></category>
            <dc:creator><![CDATA[Daniel Rusnok]]></dc:creator>
            <pubDate>Mon, 05 Oct 2020 17:15:31 GMT</pubDate>
            <atom:updated>2026-03-10T18:30:49.126Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How to Switch the Algorithms at Runtime with Strategy Pattern in C#]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/net-core/how-to-switch-the-algorithms-at-runtime-with-strategy-pattern-in-c-43fec29a1702?source=rss----9864284e8ea3---4"><img src="https://cdn-images-1.medium.com/max/2000/1*t4j5VcPrslpIBhwNGEsxsA.jpeg" width="2000"></a></p><p class="medium-feed-snippet">Answers to When, Why, and How. The quick and easy introduction to Strategy Pattern in C#</p><p class="medium-feed-link"><a href="https://medium.com/net-core/how-to-switch-the-algorithms-at-runtime-with-strategy-pattern-in-c-43fec29a1702?source=rss----9864284e8ea3---4">Continue reading on .NET Core »</a></p></div>]]></description>
            <link>https://medium.com/net-core/how-to-switch-the-algorithms-at-runtime-with-strategy-pattern-in-c-43fec29a1702?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/43fec29a1702</guid>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[Daniel Rusnok]]></dc:creator>
            <pubDate>Fri, 05 Jun 2020 07:38:36 GMT</pubDate>
            <atom:updated>2026-03-10T18:42:25.441Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[Decoupling with Chain of Responsibility Pattern in C#]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/net-core/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923?source=rss----9864284e8ea3---4"><img src="https://cdn-images-1.medium.com/max/2000/1*Pvj3dm_mU1ooMANj9Rdw1w.jpeg" width="2000"></a></p><p class="medium-feed-snippet">The Chain of Responsibility Pattern allows us to easily separate dependent parts to make code more extensible and testable.</p><p class="medium-feed-link"><a href="https://medium.com/net-core/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923?source=rss----9864284e8ea3---4">Continue reading on .NET Core »</a></p></div>]]></description>
            <link>https://medium.com/net-core/decoupling-with-chain-of-responsibility-pattern-in-c-1273329ed923?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/1273329ed923</guid>
            <category><![CDATA[development]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[Daniel Rusnok]]></dc:creator>
            <pubDate>Wed, 20 May 2020 09:29:51 GMT</pubDate>
            <atom:updated>2026-03-10T18:43:52.075Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[How to manage states with State Design Pattern in C#?]]></title>
            <description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa?source=rss----9864284e8ea3---4"><img src="https://cdn-images-1.medium.com/max/800/1*oTI0l48lkqZwCwyj_vDvZA.jpeg" width="800"></a></p><p class="medium-feed-snippet">Everything you need to know about State Design Pattern in C# at one place.</p><p class="medium-feed-link"><a href="https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa?source=rss----9864284e8ea3---4">Continue reading on .NET Core »</a></p></div>]]></description>
            <link>https://medium.com/net-core/how-to-manage-states-with-state-design-pattern-in-c-d4ca47ec6aa?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/d4ca47ec6aa</guid>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[coding]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[development]]></category>
            <dc:creator><![CDATA[Daniel Rusnok]]></dc:creator>
            <pubDate>Thu, 07 May 2020 12:11:51 GMT</pubDate>
            <atom:updated>2026-03-10T18:45:38.616Z</atom:updated>
        </item>
        <item>
            <title><![CDATA[In-memory & Distributed (Redis) Caching in ASP.NET Core]]></title>
            <link>https://medium.com/net-core/in-memory-distributed-redis-caching-in-asp-net-core-62fb33925818?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/62fb33925818</guid>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[rest-api]]></category>
            <dc:creator><![CDATA[Sena Kılıçarslan]]></dc:creator>
            <pubDate>Sun, 26 Jan 2020 10:34:49 GMT</pubDate>
            <atom:updated>2020-01-26T14:35:46.915Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post, I will demonstrate how to use in-memory caching and Redis based distributed caching in an ASP.NET Core Web API.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9qvRBVFpn9BMQxQYMs0FpA.jpeg" /></figure><p>I will use the following tools &amp; technologies:</p><ul><li>ASP.NET Core 3.1</li><li>Visual Studio 2019</li><li>Redis</li></ul><p>Let’s start with the definition of caching.</p><h4>Caching</h4><p>A <strong>cache </strong>is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere. A <strong><em>cache hit</em></strong> occurs when the requested data can be found in a cache, while a <strong><em>cache miss</em></strong> occurs when it cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower data store; thus, the more requests that can be served from the cache, the faster the system performs.<a href="https://en.wikipedia.org/wiki/Cache_(computing)">[1]</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/387/1*c9TiEegDmG5RNdU3UpPxtg.png" /><figcaption><a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside">Image source</a></figcaption></figure><p>Caching can significantly improve the performance and scalability of an app by reducing the work required to generate content. Caching works best with data that changes infrequently <strong>and</strong> is expensive to generate. Caching makes a copy of data that can be returned much faster than from the source. Apps should be written and tested to <strong>never</strong> depend on cached data.<a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1">[2]</a></p><p>Now, I will present the API in which we will use caching.</p><h4>About the API</h4><p>The API returns the movies of an actor/actress given as a parameter and looks like below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/649/1*mFn1rcCbu9tz-6pTJL2BmA.png" /></figure><p>It makes an external API call to the <a href="https://www.themoviedb.org/">TMDb </a>API and the controller code is shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VnX3PteRflmEExEtxAfpYg.png" /></figure><p>As you see, an external API call is made for every query. However, there will be cases like the same actor/actress is queried several times by the same or different users. So, we can increase the performance of this application by using caching mechanism instead of calling the external API again and again for the same actors/actresses.</p><p>First, let’s see how we can implement in-memory caching in this API.</p><h4>In-memory Caching</h4><blockquote>ASP.NET Core supports several different caches. The simplest cache is based on the <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.memory.imemorycache">IMemoryCache</a>. IMemoryCache represents a cache stored in the memory of the web server. Apps running on a server farm (multiple servers) should ensure sessions are sticky when using the in-memory cache. Sticky sessions ensure that subsequent requests from a client all go to the same server.<a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1">[3]</a></blockquote><p>In-memory caching is a <em>service</em> that’s referenced from an app using <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1">Dependency Injection</a>. So, we first need to register this service to the built-in IoC container of ASP.NET Core by modifying ConfigureServices method of Startup.cs as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JkE_YxMmblPS1GzqyjACaQ.png" /></figure><p>Then we inject IMemoryCache to the constructor:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zWuvUvCZMQT6-C_Lf584cA.png" /></figure><p>And we change GetMovieList method as below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*byH5QRW12GE60uFEcPOuSQ.png" /></figure><p>As you see in the above code, we first check if the movie list exists in the cache and return from there if it does. Otherwise, we call the external API and cache the result.</p><p>Besides, we set the cache expiration options which are explained below:</p><p>SlidingExpiration: Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. This will not extend the entry lifetime beyond the absolute expiration (if set).</p><p>AbsoluteExpiration: Gets or sets an absolute expiration date for the cache entry.</p><blockquote>A cached item set with a sliding expiration only is at risk of becoming stale. If it’s accessed more frequently than the sliding expiration interval, the item will never expire. Combine a sliding expiration with an absolute expiration to guarantee that the item expires once its absolute expiration time passes.<a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-3.1">[4]</a></blockquote><p>In the next section, we will implement distributed caching for the same controller.</p><h4>Distributed Caching</h4><blockquote>A distributed cache is a cache shared by multiple app servers, typically maintained as an external service to the app servers that access it. A distributed cache can improve the performance and scalability of an ASP.NET Core app, especially when the app is hosted by a cloud service or a server farm.</blockquote><p>A distributed cache has several advantages over other caching scenarios where cached data is stored on individual app servers. When cached data is distributed, the data:</p><ul><li>Is <em>coherent</em> (consistent) across requests to multiple servers.</li><li>Survives server restarts and app deployments.</li><li>Doesn’t use local memory.<a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed?view=aspnetcore-3.1">[5]</a></li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/794/1*CO1UYESUIzbJMVT93q-I9g.png" /><figcaption><a href="https://docs.microsoft.com/en-us/azure/architecture/best-practices/caching">Image source</a></figcaption></figure><p>There are two main disadvantages of the shared caching approach:</p><ul><li>The cache is slower to access because it is no longer held locally to each application instance.</li><li>The requirement to implement a separate cache service might add complexity to the solution.<a href="https://docs.microsoft.com/en-us/azure/architecture/best-practices/caching">[6]</a></li></ul><p>We will use Redis-based distributed caching in our API. So let’s continue with what Redis is and how to install it.</p><p><strong>Redis</strong></p><p>The name Redis means <strong>RE</strong>mote <strong>DI</strong>ctionary <strong>S</strong>erver.</p><blockquote>Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams.<a href="https://redis.io/">[7]</a></blockquote><p>Now, I will show how to install Redis in a Windows machine using Chocolatey. <em>(If you don’t have Chocolatey you can install it from </em><a href="https://chocolatey.org/docs/installation"><em>here</em></a><em>.)</em></p><p>First, install the <a href="https://chocolatey.org/packages/redis-64/">Chocolatey Redis package</a>.</p><p>Then run redis-server from a command prompt.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dQYysU48t-m-LnLAGzPduQ.png" /></figure><p>Now, the Redis server is up and running. We can test it using the redis-cli command.</p><p>Open a new command prompt and run redis-cli on it and try the following commands:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/419/1*IoNNB78fuMPBk2zcfpP0KA.png" /></figure><p>Now that we installed the Redis-server and saw that it is working properly, we can modify the API to use Redis-based distributed caching.</p><p><strong>Modify the API</strong></p><p>ASP.NET Core provides <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.distributed.idistributedcache">IDistributedCache</a> interface to interact with the distributed caches including Redis.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FaWjiGiYE1OqsyepV-Opig.png" /></figure><ul><li>Get , GetAsync : Accepts a string key and retrieves a cached item as a byte[] array if found in the cache.</li><li>Set , SetAsync : Adds an item (as byte[] array) to the cache using a string key.</li><li>Refresh , RefreshAsync : Refreshes an item in the cache based on its key, resetting its sliding expiration timeout (if any).</li><li>Remove , RemoveAsync : Removes a cache item based on its string key.</li></ul><p>To use a Redis distributed cache, we need to add a package reference to <a href="https://www.nuget.org/packages/Microsoft.Extensions.Caching.StackExchangeRedis">Microsoft.Extensions.Caching.StackExchangeRedis</a> package in our project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/981/1*omozAnqyzdlhKZkQHLG7fw.png" /></figure><p>Then we add the following line to ConfigureServices method of the Startup.cs for configuration the cache implementation using a <a href="https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.caching.stackexchangeredis.rediscache">RedisCache</a> instance:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3b4JtGGBJbokgy3Kjm5Yqg.png" /></figure><p>Our approach in modifying the controller will be very similar to what we did in the in-memory caching section.</p><p>First, we inject IDistributedCache to the controller:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*waNM2JUZ6w6ACSUB740cXA.png" /></figure><p>Then we change the GetMovieList method as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*29O6nj7QAzYP-WY2HPlArw.png" /></figure><p>As mentioned above, GetAsync and SetASync methods work with byte[] arrays, so I encoded the serialized movies list to byte[] array. Also, I want to mention that there are extension methods where you do not need to convert the value to byte[] array and these are GetStringAsync and SetStringAsync.</p><p>You can find the source code in this <a href="https://github.com/kilicars/AspNetCoreCaching">GitHub repository</a>.</p><p>That’s the end of the post. I hope you found this post helpful and easy to follow. If you have any questions and/or comments, you can share them in the responses section below.</p><p>And if you liked this post, please clap your hands 👏👏👏</p><p>Bye!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=62fb33925818" width="1" height="1" alt=""><hr><p><a href="https://medium.com/net-core/in-memory-distributed-redis-caching-in-asp-net-core-62fb33925818">In-memory &amp; Distributed (Redis) Caching in ASP.NET Core</a> was originally published in <a href="https://medium.com/net-core">.NET Core</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deploy an ASP.NET Core App with EF Core and PostgreSQL to Google Cloud]]></title>
            <link>https://medium.com/net-core/deploy-an-asp-net-core-app-with-ef-core-and-postgresql-to-google-cloud-be8a06978eb0?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/be8a06978eb0</guid>
            <category><![CDATA[cloud-sql]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[deployment]]></category>
            <category><![CDATA[postgresql]]></category>
            <dc:creator><![CDATA[Sena Kılıçarslan]]></dc:creator>
            <pubDate>Wed, 22 Jan 2020 19:06:59 GMT</pubDate>
            <atom:updated>2020-01-22T19:10:23.244Z</atom:updated>
            <content:encoded><![CDATA[<p>In this post, I will show how to deploy an ASP.NET Core web application with EF Core and PostgreSQL to App Engine on Google Cloud Platform (GCP). Besides, I will show how to use Cloud SQL for PostgreSQL instance from this application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*02mp-OtucGqUl9FmU3WMvw.jpeg" /></figure><p>I will use the following tools:</p><ul><li><a href="https://console.cloud.google.com">Google Cloud Console</a></li><li><a href="https://visualstudio.microsoft.com/vs/older-downloads/">Visual Studio 2017 Community Edition</a></li><li><a href="https://cloud.google.com/tools/visual-studio/docs/quickstart#install_cloud_tools_for_visual_studio">Google Cloud Tools for Visual Studio</a></li><li><a href="https://www.pgadmin.org/">pgAdmin</a></li></ul><p>The sections of this post will be as follows:</p><ul><li>About the App</li><li>Sign up for Google Cloud</li><li>Create the GCP Project</li><li>Cloud SQL</li><li>Modify the App and Initialize the Database</li><li>Check the Database (pgAdmin)</li><li>Test</li><li>Publish To Google Cloud</li><li>Clean up the GCP Project</li></ul><p><em>In the </em><a href="https://medium.com/net-core/deploy-an-asp-net-core-app-to-google-cloud-d5ff3ff99b2d"><em>previous post</em></a><em>, I demonstrated how to deploy an ASP.NET Core web application starter project to GCP. As it has no database access, it is a simpler version of this one and I recommend you start from there if this is your first deployment to GCP.</em></p><p>If you are ready, let’s get started.</p><h4>About the App</h4><p>The application that we will deploy manages a database of TV Shows and its main page looks like below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yx8mObsKAcQ4NGx7vgciHw.png" /></figure><p>The app is an ASP.NET Core MVC web application and it uses EF Core to perform CRUD operations on a PostgreSQL database.</p><p>You can download the source code from this <a href="https://github.com/kilicars/AspNetCoreAppPostgreSQL">GitHub repository</a>.</p><h4>Sign up for Google Cloud</h4><p>If you don’t have a Google Cloud account, go to <a href="https://cloud.google.com/">Google Cloud web site</a> and click <strong><em>Get started for free</em> </strong>button. Sign up with your Gmail account and get $300 (for free) to spend on Google Cloud Platform over the next 12 months.</p><h4>Creating the GCP Project</h4><p>Next, go to <a href="https://console.cloud.google.com">Google Cloud Console</a> and type <strong><em>App Engine</em></strong> in the search box and click <strong><em>Create</em></strong> in the dashboard of the App Engine to create a new project:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xyDrIYHCfepgfrHUOrQmEA.png" /></figure><p>Name your project and then select your region and enable billing in the next page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/514/1*effwr_HFBir3EQu-CBWqWA.png" /></figure><h4>Cloud SQL</h4><p>Now, we will create our PostgreSQL instance in the Cloud SQL.</p><blockquote>Cloud SQL is a fully-managed database service that makes it easy to set up, maintain, manage, and administer your relational databases on Google Cloud Platform. You can use Cloud SQL with either <a href="https://cloud.google.com/sql/docs/mysql/">MySQL</a> or <a href="https://cloud.google.com/sql/docs/postgres/">PostgreSQL</a>.</blockquote><p>First, select <strong><em>APIs &amp; Services -&gt; Library</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/469/1*igV0i2bhAJcUYB2sqbsMDg.png" /></figure><p>and in the next page search for <strong><em>Cloud SQL API</em></strong> and <strong><em>Enabl</em></strong>e this API:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/710/1*18PfHdxRhzap2BqsaqwCEg.png" /></figure><p>Next, search for <strong><em>Cloud SQL</em></strong> and click <strong><em>Go To Cloud SQL</em></strong> in the following page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/707/1*Pqw5-o-U0uuswmPDdwt_OQ.png" /></figure><p>Click <strong><em>Create Instance</em></strong> in the next page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/416/1*0M6UFah8whNeqMWto302jA.png" /></figure><p>and then choose <strong><em>PostgreSQL</em></strong>.</p><p>On the next page, give an ID to your instance and set your password for the default <em>(</em><strong><em>postgres</em></strong><em>)</em> user.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/509/1*O98NtkTODBE8rMccA4aDhg.png" /></figure><p><em>We will use this Id and password to connect to this instance later in the tutorial.</em></p><p>Next, the <strong><em>Compute Engine API</em></strong> creates the instance and it takes a few minutes.</p><p>After you see the green tick on the instance ID, click on the instance to go to <strong><em>Instance Details </em></strong>page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*w90rVEjTVbOYuGRDnb4dsA.png" /></figure><p>In the <strong><em>Overview</em></strong> tab, you can see the IP and connection name for the instance:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/542/1*1VN4vfYvnXxlVGPDs8Xysw.png" /></figure><p>Next, go to the <strong><em>Connections</em></strong> tab and click <strong><em>Add Network</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/485/1*yyPB_NunV6haSuLZuDJiYw.png" /></figure><ul><li>Enter all for the name.</li><li>Enter 0.0.0.0/0 for the network.</li><li>Click <strong><em>Done</em></strong>, then <strong><em>Save</em></strong>.</li></ul><p><strong>Security Issues</strong></p><p>As you see, we gave access to public IPs to make login attempts to the instance. SSL encryption is recommended when using Public IP to connect to the instance.</p><p><em>For the sake of simplicity, I will not implement this part in this tutorial. If you want to implement this, please follow the instructions </em><a href="https://cloud.google.com/appengine/docs/flexible/dotnet/using-cloud-sql-postgres#configure_ssl_access_to_the_cloud_sql_instance"><em>here</em></a><em>. Besides, you will need to modify your app too. If you need help in this part, let me know in the comments.</em></p><h4>Modify the App and Initialize the Database</h4><p>In Visual Studio, open the application and modify the connection string in <strong><em>appsettings.json</em></strong>:</p><pre>&quot;ConnectionStrings&quot;: {</pre><pre>&quot;TvShowsContext&quot;: &quot;Uid=<strong>[YOUR_USER_NAME]</strong>;Pwd=<strong>[YOUR_PASSWORD]</strong>;Host=<strong>[YOUR_INSTANCE_IP]</strong>;Database=TvShowsContext-fe1316f5-48e2-4739-ae44-eccbe217ab10&quot;</pre><pre>}</pre><p>If you haven’t created your migration file already, run the following command in the <strong><em>Package Manager Console</em></strong>:</p><pre>Add-Migration Initial</pre><p>and then run the below command to create the database on the instance:</p><pre>Update-Database</pre><h4>Check the Database (pgAdmin)</h4><p>Now, we will check the database created on Cloud SQL using <a href="https://www.pgadmin.org/">pgAdmin</a>.</p><p>Launch pgAdmin and then select <strong><em>Servers -&gt; Create -&gt; Server:</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/446/1*-rX2M29zpr_hhv5HPZ0tPQ.png" /></figure><p>and then fill in the fields shown in the red box below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/504/1*OuqVN-Ys7p6yVYER4tOpKA.png" /></figure><p>As you see below, our database and tables were created on the Cloud SQL:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/427/1*skiZe5hcMGWrAJivc3MpuA.png" /></figure><h4>Test</h4><p>Now, we will test the application on our local computer.</p><p><em>In this test configuration, our application server is on localhost and the database server is on the cloud.</em></p><p>Run the application on the Visual Studio and click <strong><em>TVShowsApp</em></strong> on the web page and then <strong><em>Create New</em></strong> on the next page.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/572/1*BlXEuBUtv56IuT1gE5tuhg.jpeg" /></figure><p>After creating our first record, it is shown below on the main page:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W4wVTTP0q1umPB-XRuABCg.png" /></figure><p>We can check the record created using<strong><em> pgAdmin </em></strong>as well:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/874/1*QqJ4CGLXhK6Qw176cXEveg.png" /></figure><h4>Publish to Google Cloud</h4><p>Now, we will publish our application to Google Cloud.</p><p>First, install <a href="https://cloud.google.com/tools/visual-studio/docs/quickstart#install_cloud_tools_for_visual_studio">Google Cloud Tools for Visual Studio</a> if you don’t have it already.</p><p>Open Visual Studio and go to <strong><em>Tools -&gt; Extensions and Updates. </em></strong>Search for <strong><em>Google Cloud Tools for Visual Studio</em> </strong>and click Install <em>(It’s already installed on my computer)</em>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/627/0*D5mf6VB-ne5LecYb.png" /></figure><p><em>As a side note, Cloud Tools for Visual Studio is not supported in VS 2019. So, I am using VS 2017 for this tutorial.</em></p><p>Next, we will link our application to our GCP project.</p><p>Open <strong><em>Tools -&gt; Google Cloud Tools -&gt; Manage Accounts</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/502/1*EPZ-9bMWQzrRuGNRDLXXzQ.png" /></figure><p>and select <strong><em>Add account</em></strong>. Login to the account that you used to register to the GCP.</p><p>You will see your account logged at the top right corner of Visual Studio. There is a <strong><em>No Project</em></strong> label near this account and click that and click <strong><em>Select Project</em></strong>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/502/1*d85fmDZRvuCKrqoqYz_a-Q.png" /></figure><p>Select the project that you created above.</p><p>Enable the following APIs from <a href="https://console.cloud.google.com">Google Cloud Console</a>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/667/1*ze_YgySYuj4fCwFtTkcgjg.png" /></figure><p>Next, right-click on the project in Visual Studio and select <strong><em>Publish To Google Cloud. </em></strong>Then select <strong><em>App Engine Flex</em></strong> in the next dialog.</p><p>After the deployment operation is completed, the web page is launched automatically and the application’s URL is as follows:</p><pre><a href="https://[Your-project_id].appspot.com">https://<strong>[Your-project_id]<em>.</em></strong>appspot.com</a></pre><p>I created a new record as we did in the previous section and the main page of the application running on the cloud looks like below now:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kGUiQfB4SjjB302puphv2g.png" /></figure><p>Again, we can check the new record from the database using <em>pgAdmin</em>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/906/1*Krn8A-qxZA1JpGLg0A58Zw.png" /></figure><h4>Clean up the GCP Project</h4><p>To avoid incurring charges, we can delete our GCP project to stop billing for all the resources used within the project.</p><p>In the <a href="https://console.cloud.google.com">Google Cloud Console</a>, search for <strong><em>Manage Resources</em></strong>. In this menu, select your project and click <em>Delete</em>:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/848/1*RQmgeiIZwSWQQgSPhBwo0g.png" /></figure><p>That’s the end of the post. I hope you found this post helpful and easy to follow. If you have any questions and/or comments, please let me know in the responses section below.</p><p>And if you liked this post, please clap your hands 👏👏👏</p><p>Bye!</p><h4>References</h4><p><a href="https://cloud.google.com/appengine/docs/flexible/dotnet/using-cloud-sql-postgres"><em>https://cloud.google.com/appengine/docs/flexible/dotnet/using-cloud-sql-postgres</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be8a06978eb0" width="1" height="1" alt=""><hr><p><a href="https://medium.com/net-core/deploy-an-asp-net-core-app-with-ef-core-and-postgresql-to-google-cloud-be8a06978eb0">Deploy an ASP.NET Core App with EF Core and PostgreSQL to Google Cloud</a> was originally published in <a href="https://medium.com/net-core">.NET Core</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Google Cloud Storage in ASP.Net Core]]></title>
            <link>https://medium.com/net-core/using-google-cloud-storage-in-asp-net-core-74f9c5ee55f5?source=rss----9864284e8ea3---4</link>
            <guid isPermaLink="false">https://medium.com/p/74f9c5ee55f5</guid>
            <category><![CDATA[cloud-storage]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[google-cloud-storage]]></category>
            <category><![CDATA[google-cloud-platform]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <dc:creator><![CDATA[Sena Kılıçarslan]]></dc:creator>
            <pubDate>Sun, 12 Jan 2020 21:01:23 GMT</pubDate>
            <atom:updated>2020-01-13T16:25:55.491Z</atom:updated>
            <content:encoded><![CDATA[<h3>Using Google Cloud Storage in ASP.NET Core</h3><p>In this post, I will show how to use Google Cloud Storage (GCS) to store and serve images of an ASP.NET Core web application.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/714/1*_XwClOBr4VK5bng-6RUEaw.jpeg" /></figure><p>The sections of this post will be as follows:</p><ul><li>About the App</li><li>Sign up for Google Cloud</li><li>Google Cloud Storage</li><li>Modify the App</li><li>Test the App</li><li>Clean up the Resources</li></ul><h4>About the App</h4><p>The application that we will modify manages a database of TV Shows and its main page looks like below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*orFFd--lDUgVJ_RaUivKaw.png" /></figure><p>The app is an ASP.NET Core MVC web application and it uses EF Core to perform CRUD operations on a SQL Server database. You can download the source code from this <a href="https://github.com/kilicars/AspNetCoreWebApp">Github repository</a>.</p><p><em>If you are interested in the development process of this application, you can check </em><a href="https://medium.com/net-core/building-a-web-application-using-asp-net-core-mvc-and-entity-framework-core-15ee6192b3f3"><em>this post</em></a><em>.</em></p><p>In the current version, the image of the TV show needs to exist in the <strong><em>wwwroot/images</em></strong> folder of the project and is entered as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/442/1*pmVPANP8ihGRjiN6FfIC7A.png" /></figure><p>In the modified version, the user will select the image file to be uploaded and the file will be uploaded to Google Cloud Storage in the create action.</p><p>Let’s start by performing the necessary actions in the Google Cloud Platform (GCP).</p><h4>Sign up for Google Cloud</h4><p>If you don’t have a Google Cloud account, go to <a href="https://cloud.google.com/">Google Cloud web site</a> and click <strong><em>Get started for free</em> </strong>button. Sign up with your Gmail account and get $300 (for free) to spend on Google Cloud Platform over the next 12 months.</p><h4>Google Cloud Storage</h4><p>In this section, we will do the necessary operations to use Google Cloud Storage service of the GCP for our application.</p><blockquote>Cloud Storage provides worldwide, highly durable object storage that scales to exabytes of data. You can access data instantly from any storage class, integrate storage into your applications with a single unified API, and easily optimize price and performance.</blockquote><blockquote>Cloud Storage is typically used to store unstructured data.</blockquote><p>First, we will create a new GCP project in the <a href="https://console.cloud.google.com/">Google Cloud Console</a>. <em>(You can use an existing project too). </em>Select the dropdown box shown in the red box and then click <strong><em>New Project</em></strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/792/1*8Y037ZIgeTce6m1EA3r7QA.png" /></figure><p>Give your project a name and then click Create.</p><p>Next, we will create a <strong><em>bucket</em></strong> to store our images.</p><blockquote><em>Buckets</em> are the basic containers that hold your data in Cloud Storage.</blockquote><p>Write <strong><em>Storage </em></strong>in the search box and then click <em>Storage</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*I42iNRmMvzGHCMTRtuAywQ.png" /></figure><p>Click <strong><em>Create Bucket</em></strong> on the next screen. Then,</p><ul><li>give a unique name for your bucket,</li><li>choose <strong><em>Standard </em></strong>for <strong><em>Storage class</em>,</strong></li><li>choose a suitable <strong><em>Location </em></strong>for you,</li><li>click <strong><em>Create</em></strong>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/917/1*32OcinupJ8qDO5pGjn7-eA.png" /></figure><p>After the creation is completed, we will see our bucket as follows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2Ohc_TxChBzH5X_y8GIDQw.png" /></figure><p>Next, we will make the bucket <a href="https://cloud.google.com/storage/docs/access-control#applyacls">publicly readable</a> so it can serve files:</p><ul><li>Select the <strong><em>Permissions</em></strong><em> </em>tab shown in the red box above.</li><li>Click the <strong><em>Add members</em></strong> button.</li><li>In the <strong><em>New members</em></strong><em> </em>field, enter <em>allUsers</em>.</li><li>In the <strong><em>Roles</em></strong> drop-down, select the <strong><em>Storage</em></strong><em> </em>sub-menu, and click the <strong><em>Storage Object Viewer</em></strong> option as shown below:</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/538/1*sR19UmBG0l0yHThwTh4D0A.png" /></figure><ul><li>Click <strong><em>Save</em></strong>.</li></ul><p>Now, we will set up a <a href="https://cloud.google.com/storage/docs/authentication#service_accounts">service account</a> and download credentials to run our application locally.</p><p>Enter <strong><em>APIs &amp; Services</em></strong> in the search box of the cloud console. Then,</p><ul><li>click <strong><em>Credentials </em></strong>on the left pane,</li><li>click <strong><em>Create credentials</em></strong>,</li><li>select <strong><em>Service account key</em></strong>.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lXlVXczL-PIw1arsZnBQnw.png" /></figure><p>In the next dialog, create the new service account as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/711/1*aXRxb6OJi_d3ju9Vvuqm1w.png" /></figure><p>After this operation, a new JSON file is created and we download this file to our computer to use in our project in the next section.</p><h4>Modify the App</h4><p>In this section, we will perform the modifications listed below in our application so that we will be able to upload the image of the TV show that will be used in the application:</p><ul><li>Modify the model</li><li>Modify the views</li><li>Edit the application settings</li><li>Install the Google Cloud Storage package</li><li>Create a new class for GCS operations</li><li>Edit Startup.cs</li><li>Modify the controller</li></ul><p><strong>Modify the model</strong></p><p>Change the TvShow model as seen below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/684/1*snJIVFnH0cNQie-Mij2NXw.png" /></figure><p>We added two new properties to the model:</p><ul><li>ImageFile will be used to get the image file to be uploaded</li><li>ImageStorageName will be used to keep the name of the object stored in the GCS bucket related to the image file uploaded.</li><li>MaxFileSize and PermittedExtensions are custom validation attributes used to limit the maximum file size and extensions of the image file. <em>(You can find the related classes in the </em><strong><em>CustomValidationAttributes </em></strong><em>folder of the final version of the project which will be shared at the end of the tutorial)</em></li></ul><p>Next, we will run the following commands in the<strong><em> Package Manager Console </em></strong>to update the database.</p><pre>Add-Migration ImageFile<br>Update-Database</pre><p><strong>Modify the views</strong></p><p>In the <strong><em>Create.cshtml</em></strong>, add the parts shown in red boxes below and delete the part related to ImageUrl :</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/734/1*oy01a6jc3viAJSFe2tSS5w.png" /></figure><p>Modify the <strong><em>Edit.cshtml</em></strong> as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/737/1*MIhQ0gOEs_cWmxACcUQwuA.png" /></figure><p>Next, we will add style attribute to the ImageUrl to make the height and width of image standard in the other views.</p><p>In the <strong><em>Delete.cshtml</em></strong> and <strong><em>Details.cshtml</em></strong>, change the part related to ImageUrl with the following:</p><pre>&lt;dd class=&quot;col-sm-10&quot;&gt;</pre><pre>&lt;img src=&quot;@Url.Content(Model.ImageUrl)&quot; alt=&quot;Image&quot; style=&quot;width: 150px; height: 220px;&quot;/&gt;</pre><pre>&lt;/dd&gt;</pre><p>In the <strong><em>Index.cshtml</em></strong>, change the part related to ImageUrl with the following:</p><pre>@if (item.ImageUrl != null)</pre><pre>{</pre><pre>&lt;img src=&quot;@Url.Content(item.ImageUrl)&quot; alt=&quot;Image&quot; style=&quot;width: 150px; height: 220px;&quot;/&gt;</pre><pre>}</pre><p><strong>Edit the application settings</strong></p><p>Add the following key-value pairs to <strong><em>appsettings.json</em></strong> and change the values with the values of the credential file path and GCS bucket that you created in the previous section:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/643/1*44UJ_P7z0xmYJ8lwhh4RVw.png" /></figure><p><strong>Install the Google Cloud Storage package</strong></p><p>Right-click on the project and select <strong><em>Manage Nuget Packages…</em></strong> and then install the following package:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/893/1*2Hioun5XU2FvJ8lZgXb56w.png" /></figure><p><strong>Create a new class for GCS operations</strong></p><p>Now, we will create a new class that will manage the file upload and delete operations in our GCS bucket using the client library that we installed above.</p><p>Right-click on the project and create a new folder called <strong><em>CloudStorage</em></strong>. Then add the following <strong><em>ICloudStorage</em></strong> interface to this folder.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5834f254980ac9d63df67d654ac844ae/href">https://medium.com/media/5834f254980ac9d63df67d654ac844ae/href</a></iframe><p>Next, add a class called <strong><em>GoogleCloudStorage</em></strong> which implements the <strong><em>ICloudStorage</em></strong> under this folder:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/59a0040d03f18309b0c9a98e3cc2396d/href">https://medium.com/media/59a0040d03f18309b0c9a98e3cc2396d/href</a></iframe><p>This class gets the credential JSON file name and the GCS bucket name from the application settings that we defined previously.</p><p>As seen in the implementation, it has two methods:</p><ul><li>UploadFileAsync: Uploads the file given with the parameter imageFile to the GCS with the given fileNameForStorage as object name.</li><li>DeleteFileAsync: Deletes the file with the given fileNameForStorage as object name from the GCS bucket.</li></ul><p>We will use these methods in the <strong><em>Create</em></strong>, <strong><em>Edit </em></strong>and <strong><em>Delete </em></strong>action methods of the <strong><em>TvShowsController</em></strong>.</p><p><strong>Edit Startup.cs</strong></p><p>Before using the <strong><em>GoogleCloudStorage</em></strong> class in the <strong><em>TvShowsController</em></strong>, we need to register it as a service in <strong><em>Startup.cs</em></strong> to the built-in IoC container as it will be injected to the controller via constructor injection.</p><p>Add the following line to the end of the ConfigureServices method:</p><pre>services.AddSingleton&lt;ICloudStorage, GoogleCloudStorage&gt;();</pre><p><strong>Modify the controller</strong></p><p>Now, we will modify <strong><em>Create</em></strong>, <strong><em>Edit </em></strong>and <strong><em>Delete </em></strong>action methods of the <strong><em>TvShowsController</em></strong>.</p><p>First, change the following part for the dependency injection:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/680/1*-xyuq2sA7opFtoOrLOedQw.png" /></figure><p>Second, add the following new methods to the controller class:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/231d418894694e1dad0aba84f4ec56b8/href">https://medium.com/media/231d418894694e1dad0aba84f4ec56b8/href</a></iframe><p>FormFileName method creates a new object name in the format below:</p><pre>{tv-show-title}-{current-datetime}.{file-extension}</pre><p>Then, this new name is passed as a parameter to the UploadFile method and it is used as the object name in the GCS bucket. This object name will also be stored in the TvShow.ImageStorageName field and will be used to delete the object from the GCS bucket if needed.</p><p>UploadFile method calls the UploadFileAsync method that we defined in the GoogleCloudStorage class previously. Notice that after the file is uploaded to GCS, the public URL to this file is returned and we will use this URL to serve the file directly from GCS. So we store this value in the TvShow.ImageUrl field to show the poster in the views of the application.</p><p>Next, change the Create method as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/849/1*BVnMURvEpZ2Pj9yJofAmzw.png" /></figure><p>As seen above, when we create a new TV Show, we also upload the image file <em>(if the user chooses one)</em> to the GCS using the UploadFile method.</p><p>Next, change Edit method as shown in the below image:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/897/1*PozEq614TU_x36miptyPLA.png" /></figure><p>In the Edit method, we first check if there is a related poster for this TV Show and if it exists we delete that from the GCS. Then, we upload the new image to the GCS.</p><p>Lastly, add the following code to the DeleteConfirmed method:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/544/1*AKpoJiePNDcahtiV_6TULQ.png" /></figure><p>You can find the last version of the project in this <a href="https://github.com/kilicars/AspNetCoreWebAppGoogleCloudStorage">GitHub repository</a>.</p><h4>Test the App</h4><p>Now, we will test the application and see if everything works as expected.</p><p>First, let’s create a new TV Show:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/450/1*fNB65xZ2WWiZOBGCNUbN2A.png" /></figure><p>As you see above, we can now choose the image file from our computer. After clicking the <strong><em>Create </em></strong>button, the new record including the fields related to the image is created in the database as shown below:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Sl16ohT8OoaSlqvU52tilA.png" /></figure><p>We can also check that the object is created in our bucket in the GCS:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Z3hw8wsEdEAAlTA6n2HC4A.png" /></figure><p>Notice that the ImageStorageName in the table and the <strong><em>Name </em></strong>field in the GCS bucket match.</p><p>In the Index view of the application, the poster of the image is served from the GCS using the ImageUrl field in the table.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LfLK9g7_znIcFaEAMPm88g.png" /></figure><p>Next, we will test the <strong><em>Delete </em></strong>view:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/583/1*C3LgLH9q3EYLcpfV9SaVfg.png" /></figure><p>After clicking the <strong><em>Delete </em></strong>button, if we refresh the GCS bucket we see that the image object is deleted here as well.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/957/1*Z7Kjw65wwVTObx_LKpPZiQ.png" /></figure><p>You can check the <strong><em>Edit </em></strong>view and see that everything works as expected.</p><h4>Clean up the Resources</h4><p>To avoid incurring charges, we can delete our GCP project to stop billing for all the resources used within the project.</p><p>In the <a href="https://console.cloud.google.com/">Google Cloud Console</a>, search for <strong><em>Manage Resources</em></strong>. In this menu, select your project and click <em>Delete.</em></p><p>That’s the end of the post. I hope you found this post helpful and easy to follow. If you have any questions and/or comments, please let me know in the responses section below.</p><p>And if you liked this post, please clap your hands 👏👏👏</p><p>Bye!</p><h4>References</h4><p><a href="https://cloud.google.com/storage/"><em>https://cloud.google.com/storage/</em></a></p><p><a href="https://cloud.google.com/storage/docs/quickstart-console"><em>https://cloud.google.com/storage/docs/quickstart-console</em></a></p><p><a href="https://cloud.google.com/appengine/docs/flexible/dotnet/using-cloud-storage"><em>https://cloud.google.com/appengine/docs/flexible/dotnet/using-cloud-storage</em></a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=74f9c5ee55f5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/net-core/using-google-cloud-storage-in-asp-net-core-74f9c5ee55f5">Using Google Cloud Storage in ASP.Net Core</a> was originally published in <a href="https://medium.com/net-core">.NET Core</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>