<?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:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Technical Blogs]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://blog.alumdb.com/</link><image><url>https://blog.alumdb.com/favicon.png</url><title>Technical Blogs</title><link>https://blog.alumdb.com/</link></image><generator>Ghost 4.33</generator><lastBuildDate>Fri, 20 Mar 2026 17:51:31 GMT</lastBuildDate><atom:link href="https://blog.alumdb.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)]]></title><description><![CDATA[Step-by-step guide to publishing and using ASP.Net Core 6 WebAPI apps on Google Cloud Run]]></description><link>https://blog.alumdb.com/guide-to-netcore6-and-cloud-run/</link><guid isPermaLink="false">61eb3723a801321df2042979</guid><dc:creator><![CDATA[Sam Dzirasa]]></dc:creator><pubDate>Sun, 30 Jan 2022 02:06:49 GMT</pubDate><media:content url="https://blog.alumdb.com/content/images/2022/01/Clouds_GCloud_400x267png.png" medium="image"/><content:encoded><![CDATA[<img src="https://blog.alumdb.com/content/images/2022/01/Clouds_GCloud_400x267png.png" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)"><p></p><h3 id="what-is-google-cloud-run">What is Google Cloud Run?</h3><p>The best way to describe Cloud Run is to quote Google (<em>almost verbatim</em>)</p><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center><b>About Google Cloud Run</b></center>
    </div>
    <!--
    <div style="width: 70%; margin-left: auto; margin-right: auto;">
    <hr class="heading-line" />
    </div> -->
    <div class="div-line"></div>
    <ol>
<li>Cloud Run is a managed compute platform that enables you to run containers that are invocable via requests or events. Cloud Run is serverless: it abstracts away all infrastructure management, so you can focus on what matters most &#x2014; building great applications. </li>
<li>It allows you to develop and deploy highly scalable containerized applications on a fully managed serverless platform. </li>
    
        <li>It allows you to write code your way using your favorite language</li>

<li>It is built upon the container and Knative open standards, enabling  portability of your applications </li>
        <li>
     Cloud Run automatically and horizontally scales out your container image to handle the received requests, then scales in when demand decreases. You only pay for the CPU, memory, and networking consumed during request handling. 
        </li>
    </ol>
    
<div class="div-line" style="width: 95%"></div>
    <i> Sources: <a href="https://cloud.google.com/run" target="blank">(i) About Cloud Run</a> </i><span style="margin-left: 60px">
     <a href="https://cloud.google.com/run/docs/quickstarts/prebuilt-deploy" target="_blank"><i>(ii) Automatic Scaling and Costs</i></a></span>
    
</div><!--kg-card-end: html--><p></p><p>In this guide, we will need to perform tasks in the Visual Studio environment as well as in the Google Cloud platform.</p><h3 id="setting-up-your-environment">Setting Up Your Environment</h3><!--kg-card-begin: html--><div style="padding-left: 20px;">
<div style="padding-left: 40px;">
    <ol>
        <li><b>Install Visual Studio 2022</b>
             If you do not already have Visual Studio 2022 installed, you can heard over to the link below, select your preferred version and install it: <br>
    <div style="padding-top: 10px; margin-left: 10%;">
     <a href="https://visualstudio.microsoft.com/vs/" target=" _blank">Visual Studio 2022 download</a>  
            </div>
        </li>
        <li>
            <b>Setup Google Cloud Environment</b><br>
            Follow the steps below to create and setup you Google Cloud Environment.
        </li>

    </ol>
   

    </div>

</div><!--kg-card-end: html--><!--kg-card-begin: html--><!--
<div style="padding-left: 20px;"><b>Create A Google Cloud Account</b></div>
-->
<div style="padding-left: 80px;">
    <ul>
        <li>
            <b>Obtain a Google Cloud account</b>.  If you do not already have a Google Cloud 	Account, you can heard over to the link below to obtain an account, Google may still have a $300 credit to get started: <br>
    <div style="padding-top: 10px; margin-left: 10%;">
     <a href="https://cloud.google.com/getting-started" target=" _blank">Getting Started with Google Cloud</a>  
            </div>
        </li>
            <li>
                <b>Install Google Cloud SDK</b> You will need the cloud SDK to work with Cloud Run. Follow the installation instructions at the link below:
    <div style="padding-top: 10px; margin-left: 10%;">
     <a href="https://cloud.google.com/sdk/docs/install" target=" _blank">Install Google Cloud SDK</a>  
            </div>
        </li>
    
      <div style="padding-top: 10px; margin-left: 40px !important;">
In this example we will be using the <b>gcloud-cli</b> (installed by the SDK); there&apos;s no need to install the optional App Engine components (see step 5 of the instructions for installing the SDK). Since we will be using docker to push out images to the Google Cloud platform,  we will need the <i>gcloud-cli</i> to authorize access and perform other cloud-based functions.
    </div>
   Of most interest to us are the following gcloud commands used to initialize the cli and to set it to handle authorization requests:<br>
   <div style="padding-top: 10px; margin-left: 10%;">
   
    <ul>
        <li>gcloud init</li>
        <li>gcloud auth login</li>
    </ul>
</div>
    Follow the directions at the link below to initialize gcloud and have it handle cloud authorization for Docker.
        <div style="padding-top: 10px; padding-bottom: 15px;margin-left: 10%;">
     <a href="https://cloud.google.com/sdk/docs/authorizing" target=" _blank">Initializing and Authorizing</a>  
            </div>
    <ul>
        <li>
    Run <b>gcloud auth configure-docker</b>.  Since we&apos;ll be using docker, configure docker to use gcloud to provide credentials for Google Cloud. Instructions are provided here:
           <div style="padding-top: 10px; margin-left: 10%;">
     <a https: cloud.google.com sdk gcloud reference auth configure-docker target=" _blank">Configure docker to use credentials from gcloud</a>  
            </div>
        </li>
        <li>
        Make a decison on which geographical location in which to host your application and containers. A full list of regions can be found at the link below.
               <div style="padding-top: 10px; padding-bottom: 15px;margin-left: 10%;">     
          <a href="https://cloud.google.com/docs/geography-and-regions" target="_blank">Google Cloud Regions</a><br>
                   
            </div>
            In this example, I am using <i>us-east1</i>.
        </li>
    </ul>

    </ul></div>
NOTE:  The Google Cloud SDK gets a lot of updates, so it is a good idea to check the referenced url&apos;s since the instructions may have been updated.

<hr>
<!--kg-card-end: html--><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center>
<b>Suggested Naming Convention</b></center>
    </div>
<div class="div-line"></div>
Before going any further, I suggest you come up with a naming convention for the various components we&apos;ll be using in the <i>Cloud Run</i> platform.
 <div style="margin-left: 50px; margin-top: 15px;">   
    <ol style="list-style-type:lower-alpha">
        <li>Project Name</li>
        <li>Docker Image Name</li>
        <li>Artifactory Repository Name</li>
        <li>Service Name</li>       
    </ol>
</div>
    
    If you (or your team) is working on several projects you&apos;ll find this to be very helpful.  I am naming the VS WebAPI project <i>WeatherForecastAPI</i>.  The names that I chose for this guide are:<br>
    <div style="margin-left: 50px; margin-top: 15px;">   
    <ol style="list-style-type:lower-alpha">
        <li>Project Name: <i>WeatherForecastAPI-proj</i>&#xA0;&#xA0;-- Google will generate a Project <b>ID</b> from this.</li>
        <li>Docker Image Name: <i>weatherforecastapi-image</i></li>
        <li>Artifactory Repository <i>Name: weatherforecastapi-repo</i></li>
        <li>Service Name: <i>weatherforecastapi-service</i></li>       
    </ol>
</div> 
</div><!--kg-card-end: html--><h3 id="3-create-a-google-cloud-project">3. Create a Google Cloud Project</h3><!--kg-card-begin: html--><div style="margin-left: 40px; width: 100%;">
If you are not already there, navigate to the <a href="https://console.cloud.google.com" target="_blank">Google Cloud Console</a> and create a new project. <br>
</div>

<!--kg-card-end: html--><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Cloud_Add_NewProject-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="944" height="193" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Cloud_Add_NewProject-1.png 600w, https://blog.alumdb.com/content/images/2022/01/Cloud_Add_NewProject-1.png 944w" sizes="(min-width: 720px) 720px"></figure><p>The project name is up to you, but if you want to follow along with this blog, we are naming the project <em>WeatherForecastAPI-proj</em></p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/CloudRun_CreateProject_00-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="723" height="579" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/CloudRun_CreateProject_00-1.png 600w, https://blog.alumdb.com/content/images/2022/01/CloudRun_CreateProject_00-1.png 723w" sizes="(min-width: 720px) 720px"></figure><p>Once the project is created, note the <strong><em>Project ID</em></strong>, you will need it to interact with the platform. &#xA0;Note that the ID is generated automatically, and may be different from what you expect.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Cloud_NewProject_ID.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="423" height="387"></figure><h3 id="4-create-an-artifact-repository">4. Create an Artifact Repository<br></h3><p>The next step is to create a repository to hold your containers (images). &#xA0;On the console, search for and select Artifact Registry.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/ArtifactRegistry_01.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="474" height="388"></figure><p>If prompted, enable the API.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/ArtifactRegistry_02-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="493" height="281"></figure><p>The next step is to add a repository to hold your container images. &#xA0;In this sample we will be hosting a docker image.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/ArtifactRegistry_AddRepo.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="838" height="129" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/ArtifactRegistry_AddRepo.png 600w, https://blog.alumdb.com/content/images/2022/01/ArtifactRegistry_AddRepo.png 838w" sizes="(min-width: 720px) 720px"></figure><p>Provide the repo details as shown below and click CREATE.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/ArtifactRegistry_AddRepo_02.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="732" height="809" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/ArtifactRegistry_AddRepo_02.png 600w, https://blog.alumdb.com/content/images/2022/01/ArtifactRegistry_AddRepo_02.png 732w" sizes="(min-width: 720px) 720px"></figure><p>If you run into an error like shown below, ignore it; it takes a few minutes for the repository to be propagated.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/ArtifactRegistry_02b_Error-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="400" height="223"></figure><p></p><h3 id="4-create-the-visual-studio-2022-project">4 Create the Visual Studio 2022 Project</h3><p>Start Visual Studio and create an ASP.Net Core WebAPI app. In this example, I am naming my project <em>WeatherForecastAPI</em></p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Create_Project_001_a.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="684" height="427" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Create_Project_001_a.png 600w, https://blog.alumdb.com/content/images/2022/01/Create_Project_001_a.png 684w"></figure><p>When you click &quot;Next&quot; be sure to provide the additional information as shown in the image below.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/CreateProject_02.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="855" height="618" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/CreateProject_02.png 600w, https://blog.alumdb.com/content/images/2022/01/CreateProject_02.png 855w" sizes="(min-width: 720px) 720px"></figure><p>I am using controllers, by <em>personal </em>preference. If you have any major arguments about my choice, see Ben Foster&apos;s insightful article <em><a href="https://benfoster.io/blog/minimal-apis-why-should-you-care/"> ASP.NET 6.0 Minimal APIs - Why should you care</a>?</em></p><p>After Visual Studio completes the setup, run the app (Ctrl-F5) to make sure it works.<br><br>For our purposes we are going to make some changes.</p><p>1) Replace the code in <em>Program.cs</em> with the following:</p><!--kg-card-begin: html--><pre class="line-numbers"><code class="language-csharp">
var builder = WebApplication.CreateBuilder(args);

int hostPort = Int16.Parse(builder.Configuration[&quot;Host:Port&quot;]);

builder.WebHost.UseKestrel(options =&gt;
{
    options.Listen(System.Net.IPAddress.Any, hostPort);

});

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

app.UseRouting(); 

app.UseSwagger();
app.UseSwaggerUI();

app.UseEndpoints(endpoints =&gt;
{
    endpoints.MapGet(&quot;/&quot;, async context =&gt;
    {
         context.Response.Redirect(&quot;swagger&quot;);
    });
});


//app.UseHttpsRedirection();

//app.UseAuthorization();

app.MapControllers();

app.Run();

</code></pre><!--kg-card-end: html--><p>In the above code, note the following:</p><!--kg-card-begin: html--><div style="magin-left: 30px">
    <ol type="a">
        <li>
I have commented out app.UseHttpsRedirection() - an explanation will be provided later in this blog.</li>
        <li>
            I have also commented out app.UseAuthorization &#x2014; it is not the focus of this sample</li>
        <li>I am defaulting to show the swagger UI in all the environments (Dev, Staging, Production), by design, not just  for the Dev environment.
        </li>
        <li>By default, Kestrel binds to ports 5000 and 5001. In the above code, I have generalized the code (lines 3-9), to use the port specified in <i>appsettings.json</i>.  This allows you to easily change the port to any acceptable value.  We will need this value when we configure the service in Google CloudRun.
        </li>
    
</ol></div><!--kg-card-end: html--><p></p><figure class="kg-card kg-code-card"><pre><code class="language-json">{
    &quot;Logging&quot;: {
        &quot;LogLevel&quot;: {
            &quot;Default&quot;: &quot;Information&quot;,
            &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;
        }
    },
    &quot;Host&quot;: {
        &quot;Port&quot;: 5175
    },
    &quot;AllowedHosts&quot;: &quot;*&quot;
}</code></pre><figcaption><em>appsettings.json</em></figcaption></figure><p>I have also made some changes to the WeatherForecastController to make it a little more interesting.</p><!--kg-card-begin: html--><pre class="line-numbers"><code class="language-csharp">
using Microsoft.AspNetCore.Mvc;

namespace WebApiGCloud.Controllers
{
    [ApiController]
    [Route(&quot;[controller]&quot;)]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
        &quot;Freezing&quot;, &quot;Bracing&quot;, &quot;Chilly&quot;, &quot;Cool&quot;, &quot;Mild&quot;,
        &quot;Warm&quot;, &quot;Balmy&quot;, &quot;Hot&quot;, &quot;Sweltering&quot;, &quot;Scorching&quot;
        };

        private readonly ILogger<weatherforecastcontroller> _logger;

        public WeatherForecastController(ILogger<weatherforecastcontroller> logger)
        {
            _logger = logger;
        }
        private IEnumerable<weatherforecast> GetWeatherData()
        {
            return Enumerable.Range(1, 30).Select(index =&gt; new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            }).ToArray();
        }
        [HttpGet(&quot;/weatherforecast&quot;)]
        public IEnumerable<weatherforecast> Get()
        {
            return GetWeatherData();
        }
        [HttpGet(&quot;/weatherforecast/{code:alpha:length(3,10)}&quot;)]
        public IEnumerable<weatherforecast> Get(string code)
        {
            return GetWeatherData().ToArray().Where(p =&gt; p.Summary.ToLower() == code.ToLower());
        }
    }
}

</weatherforecast></weatherforecast></weatherforecast></weatherforecastcontroller></weatherforecastcontroller></code></pre><!--kg-card-end: html--><p>Before testing locally, make sure your <em>profiles </em>section in <em>launchSettings.json </em>is similar to this:</p><figure class="kg-card kg-code-card"><pre><code class="language-json">    &quot;profiles&quot;: {
        &quot;WeatherForecastAPI&quot;: {
            &quot;commandName&quot;: &quot;Project&quot;,
            &quot;launchBrowser&quot;: true,
            &quot;environmentVariables&quot;: {
                &quot;ASPNETCORE_ENVIRONMENT&quot;: &quot;Development&quot;
            },
            &quot;applicationUrl&quot;: &quot;http://localhost:5175&quot;,
            &quot;dotnetRunMessages&quot;: true
        },</code></pre><figcaption>profile - <em>launchSettings.json</em></figcaption></figure><p>In the above <em>launchSettings.json</em>, note the following:</p><!--kg-card-begin: html--><div style="margin-left: 40px">
    <ol type="a">
        <li>
            &quot;launchUrl&quot;: &quot;swagger&quot;,  is removed; the functionailty is being handled in the code<i>&#xA0;Program.cs</i>.
        </li>
        <li>
            &quot;applicationUrl&quot; is changed to use only http, with the Kestrel port identical to the Host:Port value in <i>appSettings.json</i>
        </li>
    </ol>
</div><!--kg-card-end: html--><p></p><p>You can run the project &#xA0;(Ctrl+F5) to make sure it is working. &#xA0;You should see the Swagger UI as shown below.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/SwaggerUI.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="915" height="670" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/SwaggerUI.png 600w, https://blog.alumdb.com/content/images/2022/01/SwaggerUI.png 915w" sizes="(min-width: 720px) 720px"></figure><p>You may run into an error like this when you try to run the application. </p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/DebugError_01-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="564" height="489"></figure><p>If you &#xA0;do run into this error, check to make sure your application profile is set to <em>WeatherForecastAPI</em> as shown below</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Select_Profile.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="410" height="348"></figure><!--kg-card-begin: html--><div style="magin-left: 50px">
   You can test the API by navigating to either:
    <ul type="a">
        <li>http://localhost:5175/weatherforecast<br> OR<br></li>
        <li>http://localhost:5175/weatherforecast/hot</li>
    </ul>
</div><!--kg-card-end: html--><p>The first link will return a json that looks like:</p><!--kg-card-begin: html--><pre class="line-numbers language-json"><code>
[
    {
        &quot;date&quot;: &quot;2022-01-20T11:42:26.4073824-05:00&quot;,
        &quot;temperatureC&quot;: -18,
        &quot;temperatureF&quot;: 0,
        &quot;summary&quot;: &quot;Hot&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-21T11:42:26.4086175-05:00&quot;,
        &quot;temperatureC&quot;: 53,
        &quot;temperatureF&quot;: 127,
        &quot;summary&quot;: &quot;Cool&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-22T11:42:26.4086275-05:00&quot;,
        &quot;temperatureC&quot;: 35,
        &quot;temperatureF&quot;: 94,
        &quot;summary&quot;: &quot;Warm&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-23T11:42:26.408628-05:00&quot;,
        &quot;temperatureC&quot;: -3,
        &quot;temperatureF&quot;: 27,
        &quot;summary&quot;: &quot;Scorching&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-24T11:42:26.4086283-05:00&quot;,
        &quot;temperatureC&quot;: 33,
        &quot;temperatureF&quot;: 91,
        &quot;summary&quot;: &quot;Sweltering&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-25T11:42:26.4086287-05:00&quot;,
        &quot;temperatureC&quot;: -15,
        &quot;temperatureF&quot;: 6,
        &quot;summary&quot;: &quot;Mild&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-26T11:42:26.40863-05:00&quot;,
        &quot;temperatureC&quot;: 30,
        &quot;temperatureF&quot;: 85,
        &quot;summary&quot;: &quot;Balmy&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-27T11:42:26.4086304-05:00&quot;,
        &quot;temperatureC&quot;: -14,
        &quot;temperatureF&quot;: 7,
        &quot;summary&quot;: &quot;Chilly&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-28T11:42:26.4086314-05:00&quot;,
        &quot;temperatureC&quot;: -4,
        &quot;temperatureF&quot;: 25,
        &quot;summary&quot;: &quot;Freezing&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-29T11:42:26.4086318-05:00&quot;,
        &quot;temperatureC&quot;: -17,
        &quot;temperatureF&quot;: 2,
        &quot;summary&quot;: &quot;Cool&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-30T11:42:26.4086329-05:00&quot;,
        &quot;temperatureC&quot;: -11,
        &quot;temperatureF&quot;: 13,
        &quot;summary&quot;: &quot;Scorching&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-31T11:42:26.4086332-05:00&quot;,
        &quot;temperatureC&quot;: -15,
        &quot;temperatureF&quot;: 6,
        &quot;summary&quot;: &quot;Chilly&quot;
    },
    {
        &quot;date&quot;: &quot;2022-02-01T11:42:26.4086335-05:00&quot;,
        &quot;temperatureC&quot;: -17,
        &quot;temperatureF&quot;: 2,
        &quot;summary&quot;: &quot;Scorching&quot;
    },
    {
        &quot;date&quot;: &quot;2022-02-02T11:42:26.4086339-05:00&quot;,
        &quot;temperatureC&quot;: 9,
        &quot;temperatureF&quot;: 48,
        &quot;summary&quot;: &quot;Warm&quot;
    },
    {
        &quot;date&quot;: &quot;2022-02-03T11:42:26.4086342-05:00&quot;,
        &quot;temperatureC&quot;: -11,
        &quot;temperatureF&quot;: 13,
        &quot;summary&quot;: &quot;Freezing&quot;
    }
]
</code></pre><!--kg-card-end: html--><p>The second will return results filtered for dates where the summary is <em>Hot</em>:</p><!--kg-card-begin: html--><pre class="line-numbers language-json"><code>
[
    {
        &quot;date&quot;: &quot;2022-01-20T12:02:53.7801378-05:00&quot;,
        &quot;temperatureC&quot;: -4,
        &quot;temperatureF&quot;: 25,
        &quot;summary&quot;: &quot;Hot&quot;
    },
    {
        &quot;date&quot;: &quot;2022-01-23T12:02:53.7801469-05:00&quot;,
        &quot;temperatureC&quot;: 37,
        &quot;temperatureF&quot;: 98,
        &quot;summary&quot;: &quot;Hot&quot;
    },
    {
        &quot;date&quot;: &quot;2022-02-14T12:02:53.7801519-05:00&quot;,
        &quot;temperatureC&quot;: 25,
        &quot;temperatureF&quot;: 76,
        &quot;summary&quot;: &quot;Hot&quot;
    },
    {
        &quot;date&quot;: &quot;2022-02-17T12:02:53.7801526-05:00&quot;,
        &quot;temperatureC&quot;: 7,
        &quot;temperatureF&quot;: 44,
        &quot;summary&quot;: &quot;Hot&quot;
    }
]


</code></pre><!--kg-card-end: html--><h3></h3><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center>
<b>Why not Use Https Redirection in the Net Core App?</b></center>
    </div>
    <!--
    <div style="width: 70%; margin-left: auto; margin-right: auto;">
    <hr class="heading-line" />
    </div>-->
    <div class="div-line"></div>
    As was stated earlier, we are not using <i>app.UseHttpsRedirection</i> in the pipeline. This is because<br> <i>Cloud Run redirects all HTTP requests to HTTPS but terminates TLS before they reach your web service</i>
    <div style="margin-top: 10px;"> 
      source: https://cloud.google.com/run/docs/triggering/https-request
    </div>
        <div style="margin-top: 10px;"> 
            If you use it (UseHttpsRedirection), Kestrel will re-redirect to <i>https</i>.  This will create a circularity resulting in a <i>&quot;Too many Redirects&quot;</i> error.
    </div>
</div><!--kg-card-end: html--><h2 id="preparing-the-app-for-google-cloudrun">Preparing the App for Google CloudRun</h2><p>The steps are: </p><ul><li>Publish the app</li><li>Create a Docker image of the app</li><li>Tag the image</li><li>Push the image to Cloud Run</li></ul><h3 id="publish-the-app">Publish The App</h3><p><br>For this step, we&apos;ll use the usual Visual Studio publish steps. &#xA0;I am publishing to a <em>output </em>folder in the application directory (C:\Sample\WeatherForecastAPI\output) and setting the target framework to linux-x64</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.alumdb.com/content/images/2022/01/Publish_01_1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="710" height="475" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Publish_01_1.png 600w, https://blog.alumdb.com/content/images/2022/01/Publish_01_1.png 710w"><figcaption>Publish Settings</figcaption></figure><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center>
<b>Why Target Linux-x64 Runtime?</b></center>
    </div>
<div class="div-line"></div>
    Well.... you can target one of the windows variants instead. Just be aware that<br>
    <i>When you create an on-demand Windows Server instance on Compute Engine, Google includes the <b>cost of the license with the cost of the instance.</b></i>
    <!--
 <hr style="margin-top: 5px; margin-bottom: 5px;" />
    -->
    <div class="div-line" style="margin-top: 5px; margin-bottom: 5px; width: 95%"></div>
    Source: <a href="https://cloud.google.com/compute/docs/instances/windows/ms-licensing-faq" target="_blank">Microsoft Licensing FAQ</a>
</div><!--kg-card-end: html--><h3 id="update-your-dockerfile">Update your Dockerfile</h3><pre><code class="language-text">
FROM mcr.microsoft.com/dotnet/sdk:6.0
COPY ./output /publish
WORKDIR /publish

EXPOSE 5175
ENTRYPOINT [&quot;dotnet&quot;, &quot;WeatherForecastAPI.dll&quot;]</code></pre><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center>
<b>Note on the Dockerfile</b></center>
    </div>
<div class="div-line"></div>
    I have simplified the auto-generated Dockerfile by letting Visual Studio do the build.  Docker then just picks up the VS build output and creates the image from there.
</div><!--kg-card-end: html--><p></p><h3 id="create-the-docker-image">Create the docker image</h3><p>Using Powershell, &#xA0;switch to the application directory, and create the docker image for the project,</p><figure class="kg-card kg-code-card"><pre><code>
cd C:\Sample\WeatherForecastAPI

docker build -t weatherforecastapi-image  -f Dockerfile .</code></pre><figcaption>Use Powershell</figcaption></figure><h3 id="tag-the-image">Tag the image</h3><pre><code class="language-text">
docker tag weatherforecastapi-image us-east1-docker.pkg.dev/weatherforecastapi-proj/weatherforecastapi-repo/weatherforecastapi-image:v1.0</code></pre><p>You can tag the image version according to your needs or just use <em>latest</em>.</p><h3 id="push-the-image-to-artifact-registry">Push the Image To Artifact Registry</h3><pre><code class="language-text">
docker push us-east1-docker.pkg.dev/weatherforecastapi-proj/weatherforecastapi-repo/weatherforecastapi-image:v1.0</code></pre><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center>
<b>Docker Push Syntax</b></center>
    </div>
<div class="div-line"></div>
The syntax of the push command is:<br>
    docker push <i>REGION-docker.pkg.dev/PROJECT_ID/REPOSITORY_NAME/TAGGED_IMAGE_NAME</i><br>
For our purposes these are
<ul>
    <li>REGION: us-east1</li>
    <li>PROJECT_<b>ID</b>: weatherforecastapi-proj<br>
    		Unless they are the same, make sure it&apos;s the project_ID and not the project_NAME.
    </li>
    <li>REPOSITORY_NAME: weatherforecastapi-repo</li>
    <li>TAGGED_IMAGE_NAME: weatherforecastapi-image:v1.0</li>
 </ul>

Your Artifact Registry Docker images are stored in specific regions<br>In this sample, we are using <i>us-east1</i><br>
    A full list of regionscan be found at:<br>
    <a href="https://cloud.google.com/docs/geography-and-regions" target="_blank">Geography and Regions</a>
</div><!--kg-card-end: html--><h3 id="create-a-service">Create a Service</h3><p>Once the image has been pushed to the Artifact Registry, go to the cloud console and select your Project - -&gt; Cloud Run - -&gt; Create Service</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/image-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="1326" height="470" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/image-1.png 600w, https://blog.alumdb.com/content/images/size/w1000/2022/01/image-1.png 1000w, https://blog.alumdb.com/content/images/2022/01/image-1.png 1326w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/CloudRun_Create_Service_00.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="875" height="175" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/CloudRun_Create_Service_00.png 600w, https://blog.alumdb.com/content/images/2022/01/CloudRun_Create_Service_00.png 875w" sizes="(min-width: 720px) 720px"></figure><p>On the Create Service screen enter the values as indicated in the sample below. &#xA0;</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Screen_Top_000_a.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="580" height="635"></figure><p>To specify the container image for the service, click the SELECT - -&gt; ARTIFACT REGISTRY, and then select the image version you pushed to the repo. </p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/CloudRun_Create_Service_01b.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="611" height="414" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/CloudRun_Create_Service_01b.png 600w, https://blog.alumdb.com/content/images/2022/01/CloudRun_Create_Service_01b.png 611w"></figure><p>Enter the rest of the service parameters as shown in the sample below.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Service_Bottom-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="611" height="578" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Service_Bottom-1.png 600w, https://blog.alumdb.com/content/images/2022/01/Service_Bottom-1.png 611w"></figure><p>Note: &#xA0;In the example, I am allowing unauthenticated traffic. &#xA0;If authentication is required by your app, you&apos;ll set that up in the Kestrel pipeline in <strong><em>Program.cs</em></strong>.</p><p>Once you click the CREATE button, the process for creating and configuring the service will start.</p><p>If you are following along with this example, you will receive an error similar to this:</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Service_FailedToStart_Port_Error.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="1175" height="466" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Service_FailedToStart_Port_Error.png 600w, https://blog.alumdb.com/content/images/size/w1000/2022/01/Service_FailedToStart_Port_Error.png 1000w, https://blog.alumdb.com/content/images/2022/01/Service_FailedToStart_Port_Error.png 1175w" sizes="(min-width: 720px) 720px"></figure><p>As of this writing, you&apos;ll <em>always</em> get this error unless you set your Kestrel port to 8080. To fix this error edit and <em>re-deploy</em> the service; changing the container port to 5175, the Kestrel listening port we used in <strong><em>Program.cs</em></strong>.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Service_FailedToStart_Port_Edit_Deploy-1.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="1175" height="466" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Service_FailedToStart_Port_Edit_Deploy-1.png 600w, https://blog.alumdb.com/content/images/size/w1000/2022/01/Service_FailedToStart_Port_Edit_Deploy-1.png 1000w, https://blog.alumdb.com/content/images/2022/01/Service_FailedToStart_Port_Edit_Deploy-1.png 1175w" sizes="(min-width: 720px) 720px"></figure><p></p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Service_Container_Port_8080.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="746" height="342" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Service_Container_Port_8080.png 600w, https://blog.alumdb.com/content/images/2022/01/Service_Container_Port_8080.png 746w" sizes="(min-width: 720px) 720px"></figure><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center>
<b>Hey! Google</b></center>
    </div>
<div class="div-line" style="width: 98%"></div>
....... if you are listening, please have the Cloud Run team include the Container Port in the <b>Create Service UI</b>. This will obviate this error and the manual correction step. As of the time of this writing the <i>Container Port</i> entry shows up only in the edit step; not the create step.
<div class="div-line" style="width: 95%"></div> 
    You can also edit the YAML file to specify the correct port; if you do this, you&apos;ll also have to update the revision property.
    <b>Unfortunately,</b> as of the date of this writing, the UI for edititing the YAML file is a little kludgy; you can&apos;t scroll through it, you can&apos;t &quot;Select All&quot;, you can&apos;t copy/paste -- even in Chrome; you can&apos;t download/edit and upload a replacement.  Hopefully, the Cloud Run team will fix this. <br><br>
    In any case, this is not a show-stopper; you can always use the <i>EDIT AND DEPLOY NEW REVISION</i> button at the top of the screen.
</div><!--kg-card-end: html--><h3 id="service-deployed">Service Deployed</h3><p><br>Once you update the container port and re-deploy, the service deployment should be successful. &#xA0;You&apos;ll see a screen similar to this:</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Service_Deployed_Success.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="903" height="286" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Service_Deployed_Success.png 600w, https://blog.alumdb.com/content/images/2022/01/Service_Deployed_Success.png 903w" sizes="(min-width: 720px) 720px"></figure><p>Click on the service to take you to the details page</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Service_URL.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="1116" height="277" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Service_URL.png 600w, https://blog.alumdb.com/content/images/size/w1000/2022/01/Service_URL.png 1000w, https://blog.alumdb.com/content/images/2022/01/Service_URL.png 1116w" sizes="(min-width: 720px) 720px"></figure><h3 id="about-the-url-generated-by-cloud-run">About the URL generated by Cloud Run</h3><p><br>Note that Cloud Run generates a URL for the service automatically.<br>Cloud Run also generates a Certificate for the service and ensures only <em><strong>https</strong></em> access. &#xA0;The certificate expires in a little less than 90 days. Since Cloud Run is fully managed, the Certificate will be automatically renewed prior to expiration.</p><p>Clicking on the generated URL takes you to the Swagger UI, shown earlier when the app was tested in the development environment.</p><p>You can test the service through the Swagger UI, or by appending the URL fragments (<em>/weatherforecast</em>, <em>/weatherforecast/hot</em>), as shown earlier.</p><p>Note that the redirection to <em>https </em>is handled by the platform, and occurs <em>before </em>the Kestrel pipeline.</p><h3 id="using-a-custom-domain">Using a Custom Domain</h3><p>In case you prefer to use your own domain instead of the generated one, click on Manage Custom Domains, and then Add Mappings as shown below.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Domain_Mapping_000.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="893" height="294" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Domain_Mapping_000.png 600w, https://blog.alumdb.com/content/images/2022/01/Domain_Mapping_000.png 893w" sizes="(min-width: 720px) 720px"></figure><p></p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Domain_Mapping_01.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="1004" height="288" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Domain_Mapping_01.png 600w, https://blog.alumdb.com/content/images/size/w1000/2022/01/Domain_Mapping_01.png 1000w, https://blog.alumdb.com/content/images/2022/01/Domain_Mapping_01.png 1004w" sizes="(min-width: 720px) 720px"></figure><p>Follow the prompts to select the service to map and the custom domain to use. &#xA0;</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Add_Mapping_00.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="685" height="598" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/Add_Mapping_00.png 600w, https://blog.alumdb.com/content/images/2022/01/Add_Mapping_00.png 685w"></figure><p>In this example, I have elected to map the service to a new subdomain of this blog site (<em>alumdb.com</em>). &#xA0;After clicking the continue button Cloud Run will provide you information needed to update your DNS records as shown below.</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/DomainMapping_DNS_Records.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="699" height="546" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/DomainMapping_DNS_Records.png 600w, https://blog.alumdb.com/content/images/2022/01/DomainMapping_DNS_Records.png 699w"></figure><p>Once the service has been updated to use the custom domain the service screen will look like this:</p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/DomainMapping_Result.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="761" height="262" srcset="https://blog.alumdb.com/content/images/size/w600/2022/01/DomainMapping_Result.png 600w, https://blog.alumdb.com/content/images/2022/01/DomainMapping_Result.png 761w" sizes="(min-width: 720px) 720px"></figure><p>You can now access the service using the custom domain as shown in the examples below:</p><!--kg-card-begin: html--><div style="margin-left: 40px">
<ul>
    <li>weather.alumdb.com</li>
    <li>weather.alumdb.com/weatherforecast</li>
    <li>weather.alumdb.com/weatherforecast/hot</li>    
</ul>
<hr style="margin-top: 5px; margin-botton:5px">

Note: These links are publicly available, feel free to try them out.
</div>
<!--kg-card-end: html--><p></p><p><em>Note: &#xA0;Even after you setup your custom domain, the platform-generated service url is still available and functional. &#xA0;If you click the information icon (Show Info on Service URL&apos;s</em>) <em>you&apos;ll see both urls for the service:</em></p><figure class="kg-card kg-image-card"><img src="https://blog.alumdb.com/content/images/2022/01/Custom_URLs.png" class="kg-image" alt="Guide to Publishing ASP.Net Core 6 WebAPI Apps on Google CloudRun (GCP)" loading="lazy" width="530" height="271"></figure><p></p><!--kg-card-begin: html--><div style="border: 1px solid gray; padding: 10px; border-radius: 10px;background-color: coral">
    <div style="width: 95%; margin: auto; margin-bottom: 5px;"><center>
<b>Summary</b></center>
    </div>
<div class="div-line"></div>
    This post has provided a step-by-step guide to implementing and deploying an ASP.NET Core 6 WebAPI to Google Cloud Run.  If you are new to Cloud Run, I hope this helps you over the rough edges.
</div><!--kg-card-end: html--><p></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Using RedisJSON with NReJSON]]></title><description><![CDATA[This blog covers how to use RedisJSON in your NetCore apps. 

It provides C# examples on creating JSON records,  reading JSON data,
Changing Values and and Executing Direct Redis Commands that are not implemented in the Redis Client.]]></description><link>https://blog.alumdb.com/using-redisjson/</link><guid isPermaLink="false">61eb3723a801321df2042973</guid><category><![CDATA[RedisJSON]]></category><category><![CDATA[NReJSON]]></category><dc:creator><![CDATA[Sam Dzirasa]]></dc:creator><pubDate>Sun, 16 Aug 2020 18:00:00 GMT</pubDate><media:content url="https://blog.alumdb.com/content/images/2020/08/RedisJSON_01_01.png" medium="image"/><content:encoded><![CDATA[<h3></h3><img src="https://blog.alumdb.com/content/images/2020/08/RedisJSON_01_01.png" alt="Using RedisJSON with NReJSON"><p>This blog explores use of &#xA0;the RedisJSON datatype in NetCore applications. It provides sample C# code that you can modify and use in your own applications. In all the examples shown, we&apos;ll be using the .NET &#xA0;client NReJSON &#xA0;(courtesy, <a href="https://github.com/tombatron/NReJSON">Tommy Hanks et al</a>). </p><p><strong>Prerequisites</strong><br>The samples reference the following packages from nuget.org:<br><br>	a) &#xA0;StackExchange.Redis -Version 2.1.58<br>	b) &#xA0;NReJSON -Version 3.1.0<br>	c) &#xA0;Newtonsoft.Json -Version 12.0.3</p><p>The samples were developed and tested with the versions shown; I imagine they&apos;ll work with later versions.</p><h3 id="sample-data">Sample Data</h3><p>For this exercise, we&apos;ll need to create a RedisJSON datatype. &#xA0;We&apos;ll create a controller with a POCO object <em>UserInfo </em>that will provide the initial data for the app. &#xA0;The controller is shown below.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

//RejSon
using StackExchange.Redis;
using NReJSON;
using Newtonsoft.Json;
using System.Dynamic;

namespace RedisSample.Controllers
{
    public class Address
    {
        public string street { get; set; }
        public string city { get; set; }
        public string state { get; set; }
        public string zip { get; set; }
    }
    public class UserInfo
    {
        public string userId { get; set; }
        public string fullName { get; set; }
        public string email { get; set; }
        public int rank { get; set; }
        public Address address { get; set; }
    }
    [Route(&quot;api/[controller]&quot;)]
    public class RedisJsonSampleController : ControllerBase
    {
        UserInfo _userInfo;
        private readonly IConnectionMultiplexer _connectionMultiplexer;
        public RedisJsonSampleController(){
            UserInfo userInfo = new UserInfo()
            {
                userId = &quot;AC-67292&quot;,
                fullName = &quot;Bob James&quot;,
                email = &quot;bobjames@sample.com&quot;,
                rank = 15,
                address = new Address()
                {
                    street = &quot;678 Winona Street&quot;,
                    city = &quot;New Medford&quot;,
                    state = &quot;Delaware&quot;,
                    zip = &quot;12345&quot;
                }
            };

            _userInfo = userInfo;
            
            //Replace with the IP of your redis server
            string conn = &quot;127.0.0.1:6379&quot;; 
            _connectionMultiplexer = ConnectionMultiplexer.Connect(conn);
        }
    }
}
</code></pre><figcaption><strong>RedisJson Sample Controller</strong></figcaption></figure><p>To keep the code simple and to the point, I am creating the data in the constructor and also hard-coding the IP address of the redis server. Included in the <em>UserInfo </em>is &quot;rank&quot;. &#xA0;This is just to demonstrate use of the INCR command in a later example. &#xA0;Feel free to use DI and other methods to improve your implementation. <br><br>I am also using [HttpGet] for all the API endpoints, even if the method changes data, &#xA0;&#x2013; again this is to keep things simple.</p><p>At any point you can verify the <em>initial (</em>non-RedisJSON<em>) </em>data using this code:</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        [HttpGet]
        [Route(&quot;/api/redisjsonsample/initial/person&quot;)]
        public IActionResult GetPersonData()
        {
            return Ok(_userInfo);
        }</code></pre><figcaption><strong>Get Initial Data</strong></figcaption></figure><p>The next section of code creates the RedisJSON record and verifies that it is correct. &#xA0;Note: throughout this sample, I am using the format userprofile:useremail , in this case &#x2013; &quot;<em>userprofile:bobjames@sample.com</em>&quot; &#xA0;&#x2013; as the key.<br></p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        //********Save UserInfo to Redis**********
        [HttpGet]
        [Route(&quot;/api/redisjsonsample/save/profile&quot;)]  //save initial data
        public async Task&lt;IActionResult&gt; SaveUserProfile()
        {
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();
            string json = JsonConvert.SerializeObject(_userInfo);
            //use try/catch to catch error
            OperationResult result = await db.JsonSetAsync(key, json);

            if (result.IsSuccess) { return Ok(&quot;SaveUserProfile Succeeded&quot;); }

            return BadRequest(&quot;SaveUserProfile  Failed&quot;);
        }

 		//********Read UserInfo from Redis**********
        [HttpGet]
        [Route(&quot;/api/redisjsonsample/get/profile&quot;)]  
        public async Task&lt;IActionResult&gt; GetUserProfile()
        {
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();    
            string[] parms = { &quot;.&quot; };
            RedisResult result = await db.JsonGetAsync(key, parms);
            if (result.IsNull) { return BadRequest(&quot;GetUserProfile Failed&quot;); }
            string profile = (string)result;
            return Ok(profile);
        }</code></pre><figcaption><strong>Create Record and Check it.</strong></figcaption></figure><p><strong>Changing Values</strong><br>With the RedisJSON record created, we can try to change some properties. &#xA0;The code below shows how to change the &quot;<em>state</em>&quot; <strong>and </strong>the &quot;<em>fullName</em>&quot;</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        [HttpGet]
        [Route(&quot;/api/redisjsonsample/change/state_plus_fullname&quot;)]
        public async Task&lt;IActionResult&gt; ChangeSomeProps()
        {
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();
            
            //change state -- note: full path is needed .address.state 
            //use try/catch to catch error
            OperationResult result = await db.JsonSetAsync(key, JsonConvert.SerializeObject(&quot;New York&quot;), &quot;.address.state&quot;);
            string resultValue = result.RawResult.ToString();  //- - &gt; OK
           
            //change fullName
            //use try/catch to catch error
            OperationResult res = await  db.JsonSetAsync(key, JsonConvert.SerializeObject(&quot;Bob T Jones&quot;),&quot;.fullName&quot;);

            string fullNameResult = res.RawResult.ToString(); //- - &gt; OK  

            return Ok(resultValue + &quot;**&quot; + fullNameResult ) ; 
        
        }</code></pre><figcaption><strong>Change state and fullName</strong></figcaption></figure><p><strong>Incrementing Numeric Values</strong><br>Below is a sample on how to increment a numeric value. &#xA0;Here, we are incrementing the <em>rank </em>by 3.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">       [HttpGet]
        [Route(&quot;/api/redisjsonsample/incr/rank&quot;)]
        public async Task&lt;IActionResult&gt; IncrementRank()
        {
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();

            //note the value is being incremented by 3 -- hardcoded
            //In real life you&apos;ll pass it in as a parameter
			//use try/catch to catch error
            RedisResult res = await db.JsonIncrementNumberAsync(key, &quot;.rank&quot;, 3);           
            //returns 15 + 3   --&gt; 18  (the first time it&apos;s used)
            return Ok((string)res);
        }</code></pre><figcaption><strong>Incrementing Numeric Values</strong></figcaption></figure><p>Note: in the sample above, you can use a negative number if you want to <em>decrement </em>the number.</p><p>NReJSON also has the <strong>JsonMultiplyNumberAsync</strong> method for multiplying a numeric value. &#xA0;The code is similar to the above.</p><p><strong>Getting Multiple Properties</strong><br>Redis Records can be 512 megabytes in size. &#xA0;If you have huge JSON records you may want to limit your queries to specific keys &#xA0;&#x2013; for the same reasons why you may not want to limit use of <em>SELECT *</em> in SQL queries &#x2013; and instead use <em>SELECT column1, column3...</em> instead.</p><p>The example below illustrates how to select <em>fullName</em>, <em>state </em>and <em>zip </em>from the RedisJSON &#xA0;record.<br></p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        [HttpGet]
        [Route(&quot;/api/redisjsonsample/mprops&quot;)]
        public async Task&lt;IActionResult&gt; GetMultipleProps()
        {          
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();
            string[] multipleKeys = { &quot;.fullName&quot;, &quot;.address.state&quot; , &quot;.address.zip&quot; };
            RedisResult result = await db.JsonGetAsync(key, multipleKeys       

            if (result.IsNull)
            {
                return BadRequest(&quot;bad request&quot;);
            }
            return Ok( (string)result);
             //NOTE: returns {&quot;.fullName&quot;:&quot;Bob T Jones&quot;,&quot;.address.state&quot;:&quot;New York&quot;,&quot;.address.zip&quot;:&quot;12345&quot;}
        }</code></pre><figcaption><strong>Getting Multiple Properties</strong></figcaption></figure><p><strong>Important Notes</strong><br>a) In this example the record is returned with preceding periods (&quot;.&quot;) for the properties because they are actually redis key/value pairs.</p><p> &#xA0; &#xA0; {&quot;.fullName&quot;:&quot;Bob T Jones&quot;,&quot;.address.state&quot;:&quot;New York&quot;,&quot;.address.zip&quot;:&quot;12345&quot;}<br><br>b) If you include a key (eg .<em>middleInitial </em>) that does not exist, you will get a <em>RedisServerException </em>error and no records will be returned.</p><p><strong>Adding a New Property</strong><br>In the <em>.address.zip</em> key on the preceding samples &#xA0;we had a zip code &quot;12345 Supposing we have to add a new field, <em>zip+4</em>, without changing the original zip. The example below shows how to do that. &#xA0;Note this requires just adding the value to a <em>new </em>key, eg, &#xA0;<em>.address.zipPlusFour</em> as shown in the example below.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">   		[HttpGet]
        [Route(&quot;/api/redisjsonsample/add/addziplusfour&quot;)]
        public async Task&lt;IActionResult&gt; AddNewProperty()
        { 
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();

            string zipPlusFour = &quot;12345-6789&quot;;
            string jsonZipPlusFour = JsonConvert.SerializeObject(zipPlusFour);
            OperationResult opResult = await db.JsonSetAsync(key, jsonZipPlusFour, &quot;.address.zipPlusFour&quot;);

            if (opResult.IsSuccess)
            {
                string result = opResult.RawResult.ToString();
                return Ok(result); // --&gt; OK
            }
            return BadRequest(&quot;Error - cannot create new property&quot;);
        }</code></pre><figcaption><strong>Adding a new Property</strong></figcaption></figure><p><strong>Adding Whole Objects to &#xA0;RedisJSON Datatype</strong><br>The preceding example shows how to add a new property to the JSON record. &#xA0;We can use the same approach to add entire objects. Let&apos;s say we wanted to add a s<em>hipTo </em>address to our current <em>.address </em>key. &#xA0;In our C# code we will create a new <em>Address </em>object and add it as shown below.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp"> 		[HttpGet]
        [Route(&quot;/api/redisjsonsample/add/secondaddress&quot;)]
        public async Task&lt;IActionResult&gt; AddJsonObject()
        {
            Address shipTo = new Address()
            {
                 street=&quot;4592 Vacation Drive&quot;,
                 city = &quot;Vacation City&quot;,
                 state = &quot;New Hampshire&quot;,
                 zip =&quot;36448&quot;
            };
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();

            string jsonshipTo = JsonConvert.SerializeObject(shipTo);
            //Note the new key
            OperationResult opResult = await db.JsonSetAsync(key, jsonshipTo, &quot;.address.shipTo&quot;);

           if (opResult.IsSuccess)
            {
                string result = opResult.RawResult.ToString();
                return Ok(result); // --&gt; OK
            }
            return BadRequest(&quot;Error - cannot add object&quot;);
         }</code></pre><figcaption><strong>Adding Objects</strong></figcaption></figure><p>With this code implemented, we can then access the original <em>street </em>using the key <em>.address.street</em> and the shipTo street as <em>.address.shipTo.street.</em></p><p><strong>Implementing New Features/Executing Direct Commands</strong><br>As features are added to Redis, there may be commands that may not yet be implemented in your RedisJSON client. &#xA0;For instance, as of the time of this article (8/16/2020), the <strong>JSON.STRAPPEND</strong> command had not been implemented in our redis client NReJSON. &#xA0;Thanks to <a href="https://blog.marcgravell.com/2017/04/stackexchangeredis-and-redis-40-modules.html ">Marc Gravell</a> we can use the StackExchange.Redis <em>Execute/ExecuteAsync</em> methods to implement this and other new or unimplemented commands.<br><br>Below is an example of how we can run the JSON.STRAPPEND command to add <em>&quot; Boulevard&quot;</em> to the key &quot;<em>.address.shipTo.street&quot;</em>.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        [Route(&quot;/api/redisjsonsample/strappend/tostreet&quot;)]
        public async Task&lt;IActionResult&gt; AppendString()
        {
            IDatabase db = _connectionMultiplexer.GetDatabase();
            string key = &quot;userprofile:&quot; + _userInfo.email.ToLower();
            string jsonStrappend = JsonConvert.SerializeObject(&quot; Boulevard&quot;);
            int result = await db.ExecuteAsync(&quot;JSON.STRAPPEND&quot;, new object[] {key, &quot;.address.shipTo.street&quot;, jsonStrappend });  
            return Ok(result);
        }</code></pre><figcaption><strong>Executing Direct Commands</strong></figcaption></figure><p>Notes:<br>a) The JSON.STRAPPEND command returns an integer &#x2013; the length of the updated string.<br>b) You are encouraged to wrap the Execute method in a try/catch because if the path is invalid, a RedisServerException will be thrown.</p><p></p><p></p><p></p><p></p><p></p><p></p><p>			</p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Using RediSearch]]></title><description><![CDATA[How to use NRediSearch to create and search for RediSearch records..]]></description><link>https://blog.alumdb.com/using-redi-search/</link><guid isPermaLink="false">61eb3723a801321df2042974</guid><category><![CDATA[RediSearch]]></category><category><![CDATA[NRediSearch]]></category><dc:creator><![CDATA[Sam Dzirasa]]></dc:creator><pubDate>Sun, 16 Aug 2020 16:41:00 GMT</pubDate><media:content url="https://blog.alumdb.com/content/images/2020/08/RediSearch_01_1-1.png" medium="image"/><content:encoded><![CDATA[<h3 id="using-redisearch">Using RediSearch</h3><img src="https://blog.alumdb.com/content/images/2020/08/RediSearch_01_1-1.png" alt="Using RediSearch"><p>This blog covers how to use RediSearch in your .NETCore applications. &#xA0;It provides sample C# examples using the RediSearch NetCore client NRediSearch.</p><p>The article has two main sections: &#xA0;</p><!--kg-card-begin: markdown--><ol>
<li>Creating RedisSearch records</li>
<li>Searching for records.</li>
</ol>
<!--kg-card-end: markdown--><h3 id="prerequisites">Prerequisites</h3><p>This examples in this blog use the following packages from nuget.org:<br>	<br>		a) &#xA0;StackExchange.Redis -Version 2.1.58<br>		b) &#xA0;NRediSearch - Version 3.1.0	<br>		c) &#xA0;Newtonsoft.Json -Version 12.0.3</p><p>The RediSearch samples were developed and tested with the versions shown; I imagine they&apos;ll work with later versions. &#xA0;</p><h3 id="sample-data">Sample Data</h3><p>To make the examples a little more concrete, we created RediSearch records based on the SQL Server &#xA0;<em>Northwind Database</em>, scripts for which are <a href="https://github.com/Microsoft/sql-server-samples/tree/master/samples/databases/northwind-pubs">here</a> on GitHub. <br><br>To make your life easier, I have created a json file &#xA0;from the view <em>Order Details Extended</em>. &#xA0;If you want to follow along by creating your own Redis records your can download the<a href="https://alumdb.org/static/northwindorders.json"> json file northwindorders.json</a>. &#xA0;There are 2155 records in the file. &#xA0;The records look like the json below.</p><figure class="kg-card kg-code-card"><pre><code class="language-json">[{&quot;customerID&quot;:&quot;VINET&quot;,&quot;customerName&quot;:&quot;Vins et alcools Chevalier&quot;,
    &quot;address&quot;:&quot;59 rue de l&apos;Abbaye&quot;,&quot;city&quot;:&quot;Reims&quot;, &quot;postalCode&quot;:&quot;51100&quot;,&quot;country&quot;:&quot;France&quot;,
    &quot;orderID&quot;:10248,&quot;orderDate&quot;:&quot;1996-07-04T00:00:00&quot;,
    &quot;requiredDate&quot;:&quot;1996-08-01T00:00:00&quot;,&quot;shippedDate&quot;:&quot;1996-07-16T00:00:00&quot;,
    &quot;shipperName&quot;:&quot;Federal Shipping&quot;,&quot;productID&quot;:11,&quot;productName&quot;:&quot;Queso Cabrales&quot;,    
    &quot;unitPrice&quot;:14.0000,&quot;quantity&quot;:12,&quot;extendedPrice&quot;:168.0000,&quot;id&quot;:null,&quot;score&quot;:0},
{&quot;customerID&quot;:&quot;VINET&quot;,&quot;customerName&quot;:&quot;Vins et alcools Chevalier&quot;,
    &quot;address&quot;:&quot;59 rue de l&apos;Abbaye&quot;,&quot;city&quot;:&quot;Reims&quot;,&quot;postalCode&quot;:&quot;51100&quot;,&quot;country&quot;:&quot;France&quot;,
    &quot;orderID&quot;:10248,&quot;orderDate&quot;:&quot;1996-07-04T00:00:00&quot;,
    &quot;requiredDate&quot;:&quot;1996-08-01T00:00:00&quot;,&quot;shippedDate&quot;:&quot;1996-07-16T00:00:00&quot;,
    &quot;shipperName&quot;:&quot;Federal Shipping&quot;,&quot;productID&quot;:42,&quot;productName&quot;:&quot;Singaporean Hokkien Fried Mee&quot;,
    &quot;unitPrice&quot;:9.8000,&quot;quantity&quot;:10,&quot;extendedPrice&quot;:98.0000,&quot;id&quot;:null,&quot;score&quot;:0}]</code></pre><figcaption><strong>Sample Data for Orders (showing 2 records)</strong></figcaption></figure><h3 id="creating-the-data">Creating the Data</h3><p>To create the records for RediSearch we&apos;ll use the POCO class shown below.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">     public class DocCommon
    {
        public string Id { get; set; }
        public double Score { get; set; }
    }
     public class CustomerOrders : DocCommon
   {
        public string customerID { get; set; }
        public string customerName { get; set; }
        public string address { get; set; }
        public string city { get; set; }
        public string postalCode { get; set; }
        public string country { get; set; }
        public int orderID { get; set; }
        public System.Nullable&lt;System.DateTime&gt; orderDate { get; set; }
        public System.Nullable&lt;System.DateTime&gt; requiredDate { get; set; }
        public System.Nullable&lt;System.DateTime&gt; shippedDate { get; set; }
        public string shipperName { get; set; }
        public int productID { get; set; }
        public string productName { get; set; }
        public decimal unitPrice { get; set; }
        public short quantity { get; set; }
        public System.Nullable&lt;decimal&gt; extendedPrice { get; set; }
    }</code></pre><figcaption><strong>CustomerOrders Class</strong></figcaption></figure><p>Note that the <em>CustomerOrders</em> class inherits <em>DocCommon</em>. &#xA0;The rational for this will be explained in a later section.</p><p>The first thing is to create a controller for handling this example.</p><pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

using System.IO;
using Microsoft.AspNetCore.Routing;

using System.Dynamic;

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Cors;
using System.Reflection;
using System.Data.Common;
using System.Diagnostics;

//******** Needed for Redis Search *************
using Newtonsoft.Json;
using StackExchange.Redis;
using NRediSearch;
using static NRediSearch.Schema;
using NRediSearch.Aggregation;
using NRediSearch.QueryBuilder;


namespace RedisSearchApp.Controllers
{
    [Produces(&quot;application/json&quot;)]
    public class SearchController : ControllerBase
    {
        private readonly Microsoft.AspNetCore.Hosting.IWebHostEnvironment _hostingEnvironment;
        private readonly IConnectionMultiplexer _connectionMultiplexer;
        Client _rediSearchClient;
    public SearchController(IConfiguration config, IWebHostEnvironment hostingEnvironment)
        {
            _hostingEnvironment = hostingEnvironment;
            //In real life avoid hard-coding and use secure ways to specify
            //your redis-server IP
            string sconn = &quot;127.0.0.1:6379&quot;;
            _connectionMultiplexer = ConnectionMultiplexer.Connect(sconn);
            IDatabase db = _connectionMultiplexer.GetDatabase();           
             _rediSearchClient = new Client(&quot;index_orders&quot;, db);
           
            Schema schema = CreateSchema();
            _rediSearchClient.CreateIndex(schema, new Client.ConfiguredIndexOptions(Client.IndexOptions.Default));
           
        }
    }</code></pre><p>The constructor in this example is responsible for: &#xA0; &#xA0; </p><!--kg-card-begin: markdown--><ol>
<li>Establishing a connection to the Redis Server, using the .Net Redis Search Client (NRediSearch, courtesy - Marc Gravell and  Nick Craver, StackExchange)</li>
<li>Creating an index based on a provided schema.</li>
</ol>
<!--kg-card-end: markdown--><p>Note: the index can be created <em>only once</em>; a RedisServerException will occur if an attempt is made to create an existing index. To avoid this, the code below is used to check for a pre-existing index.</p><h3 id="check-if-index-exists">Check if Index Exists</h3><pre><code class="language-csharp">        private async Task&lt;bool&gt; IndexExistsAsync(string checkIndexName)
        {
            InfoResult resultParsed = await _rediSearchClient.GetInfoParsedAsync();
            string indexName = resultParsed.IndexName;
            return (indexName == checkIndexName);
        }</code></pre><p>We&apos;ll then change the last 2 lines of the constructor to:</p><pre><code class="language-csharp">            if (!IndexExistsAsync(&quot;index_orders&quot;).Result)
            {
                Schema schema = CreateSchema();
                _rediSearchClient.CreateIndex(schema, new Client.ConfiguredIndexOptions(Client.IndexOptions.Default));
            }</code></pre><p>The code for <em>CreateSchema </em>is:</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">     private Schema CreateSchema()
    {
        Schema schema = new Schema();
        schema.AddTextField(&quot;customerID&quot;, 1)
            .AddTextField(&quot;customerName&quot;,2)
            .AddTextField(&quot;orderID&quot;, 1)
            .AddTextField(&quot;city&quot;, 1)
            .AddTextField(&quot;postalCode&quot;, 1)
            .AddNumericField(&quot;extendedPrice&quot;)
            .AddTextField(&quot;productName&quot;, 1)                   
        };
        return schema;
    }</code></pre><figcaption><strong>Specify Redis Search Index Fields</strong></figcaption></figure><h3 id="performing-a-basic-search">Performing a Basic Search</h3><p>Now that we have the preliminaries out of the way, let&apos;s do a basic search. for the word &quot;Vins&quot; &#xA0; &#x2013; from the Northwind Database that we transferred to Redis. &#xA0;Shown below is the code used for the search.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp"> 		[HttpGet]
        [Route(&quot;api/search/redis/v.10/basic&quot;)]
        public async Task&lt;List&lt;CustomerOrders&gt;&gt; SearchRedisOrdersBasic()
        {
            string searchString = &quot;Vins&quot;;
            SearchResult res = await _rediSearchClient.SearchAsync(new NRediSearch.Query(searchString) { WithPayloads = true });

            List&lt;CustomerOrders&gt; customerOrdersList = new List&lt;CustomerOrders&gt;();

            foreach (Document doc in res.Documents)
            {           
                CustomerOrders _order = new CustomerOrders()
                {
                    id = doc.Id,
                    score = doc.Score,
                    customerName = (string)doc[&quot;customerName&quot;],
                    address = (string)doc[&quot;address&quot;],
                    customerID = (string)doc[&quot;customerID&quot;],
                    productName = (string)doc[&quot;productName&quot;],
                    extendedPrice = Convert.ToDecimal(doc[&quot;extendedPrice&quot;]),
                    //.. etc
                };
                customerOrdersList.Add(_order);   
            }
            return customerOrdersList;
        }</code></pre><figcaption><strong>Sample code for a word search</strong></figcaption></figure><h3 id="notes-on-the-above-code-">Notes on the above code:</h3><!--kg-card-begin: markdown--><ol>
<li>If you have been following along with the sample data, you&apos;ll notice that 10 records are ruturned. We&apos;ll cover later how to control the number of records returned by the search query.</li>
<li>We have hard-coded the search word &quot;Vins&quot;; in real-life you&apos;ll pass it as a parameter.</li>
<li><em>Only</em> the fields that are specified in the schema are searched for a match.</li>
<li>In line 6, note that the query parameter  <em>WithPayloads</em>  is set to true. Failure to do that results in <em>only</em> the document <em>id&apos;s</em> being returned by the redis-server.</li>
<li>All properties in the above example are cast to the appropriate types.  In general you don&apos;t need to do this for string fields.</li>
</ol>
<!--kg-card-end: markdown--><h3 id="improving-the-version-1-0-api-of-this-application-to-handle-new-properties-">Improving the Version 1.0 api of this application to handle new properties.</h3><p>Unlike a traditional relational database, Redis has no fixed schema for records. Consider the POCO class <em>CustomerOrders </em>in our example. The sample shown above will maintain the fixed properties we have have specified in the POCO. &#xA0;Now assuming after the app is released and populated with &#xA0;a vast number of records, we decide to add other fields to <em>CustomerOders </em> &#x2013; such as <em>ShipToAddress. &#xA0;</em>To implement this, we&apos;ll have to modify codes after line #20 ( [see Route <em> api/search/redis/v.10/basic</em> and <em>CustomerOrders </em>to accommodate the changes. &#xA0;At best, this is a hassle; so let&apos;s explore how to improve on the previous approach.<br><br>The sample implementation below shows a simple way to side-step all the issues involved. &#xA0;It will require modifying only <em>CustomerOrders</em>. &#xA0;Once that is done, you&apos;ll have to save new records to include the ShipToAddress. &#xA0;We can then avoid headaches by using a json client (Newtonsoft.Json) to do the heavy lifting. &#xA0;The code below illustrates this.</p><pre><code class="language-csharp">        [Route(&quot;api/search/redis/v2.0/basic&quot;)]
        public async Task&lt;List&lt;CustomerOrders&gt;&gt; SearchRedisOrdersBasicUseJson()
        {
            string searchString = &quot;Vins&quot;;
            SearchResult res = await _rediSearchClient.SearchAsync(new NRediSearch.Query(searchString) { WithPayloads = true });

            List&lt;CustomerOrders&gt; customerOrdersList = new List&lt;CustomerOrders&gt;();

            foreach (Document doc in res.Documents)
            {
                IEnumerable&lt;KeyValuePair&lt;string, RedisValue&gt;&gt; record = doc.GetProperties();
                string jsonRecord = JsonConvert.SerializeObject(record);
                CustomerOrders custOrders = JsonConvert.DeserializeObject&lt;CustomerOrders&gt;(jsonRecord);
                custOrders.id = doc.Id;
                custOrders.score = doc.Score;

                customerOrdersList.Add(custOrders);  
            }
            return customerOrdersList;
        }</code></pre><p>Only lines 12-22 (<em>foreach </em>bloc) are changed. &#xA0;The <em>GetProperties </em>method (line #11) returns a Redis KeyValuePair that we serialize and convert to <em>CustomerOrders</em>. This bypasses the hard coding and the casting issues of v1.0.</p><h3 id="wild-card-searches">Wild Card Searches</h3><p>The searches shown so far have been based on exact word searches. &#xA0;RediSearch also supports wildcard searches. &#xA0;<br><br>In the sample data used in this blog, one of the products is a product named <em>Flotemysost</em>, To get records for this product based on a wild card search, we can modify the example full search code to this:</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">//use an &quot;*&quot; to search for words that begin with &quot;Flotem&quot;
string searchString = &quot;Flotem*&quot;;      
SearchResult res = await _rediSearchClient.SearchAsync(new NRediSearch.Query(searchString) { WithPayloads = true });</code></pre><figcaption><strong>Sample Wild-Card Search Fragment</strong></figcaption></figure><p> The example above will return records that have words that begin with &quot;Flotem&quot;</p>]]></content:encoded></item><item><title><![CDATA[Filtering and Limiting Search Records]]></title><description><![CDATA[How to Filter and Limit RediSearch Query Results]]></description><link>https://blog.alumdb.com/filtering-search-records/</link><guid isPermaLink="false">61eb3723a801321df2042975</guid><category><![CDATA[RediSearch]]></category><category><![CDATA[NRediSearch]]></category><dc:creator><![CDATA[Sam Dzirasa]]></dc:creator><pubDate>Sun, 16 Aug 2020 02:18:17 GMT</pubDate><media:content url="https://blog.alumdb.com/content/images/2020/08/FilterAndLimit_01_01-5.png" medium="image"/><content:encoded><![CDATA[<h3 id="filtering-and-limiting-search-results">Filtering and Limiting Search Results</h3><img src="https://blog.alumdb.com/content/images/2020/08/FilterAndLimit_01_01-5.png" alt="Filtering and Limiting Search Records"><p>You can filter search results on the server side by applying a search filter &#x2013; this is similar to the SQL <em>where </em>clause.<br><br>The example below demonstrates how to <em>filter</em> the query to return only results with the <em>extendedPrice </em>between 160 and 350. &#xA0;It also introduces the query <em>Limit </em>method to return only the first 5 results &#x2013; this is similar to the <em>SELECT TOP</em> &#xA0;clause of SQL.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        [HttpGet]
        [Route(&quot;api/search/orders/filter&quot;)]
        public async Task&lt;List&lt;CustomerOrders&gt;&gt; SearchOrdersFilter()
        {
            string searchString = &quot;Vins&quot;;
            IDatabase db = _connectionMultiplexer.GetDatabase();

            NRediSearch.Query query = new NRediSearch.Query(searchString);

            //filter results on extendedPrice between 160 and 350
            query.AddFilter(new NRediSearch.Query.NumericFilter(&quot;extendedPrice&quot;, 160, 350));            
            query.Limit(0, 5); //return only the first 5 documents
            query.WithPayloads = true;
            
            SearchResult res = await _rediSearchClient.SearchAsync(query);

		//the rest of the code(below) for processing the search results 
		//is the same as shown in the earlier example(s)

            List&lt;CustomerOrders&gt; customerOrdersList = new List&lt;CustomerOrders&gt;();
            foreach (Document doc in res.Documents)
            {
                IEnumerable&lt;KeyValuePair&lt;string, RedisValue&gt;&gt; record = doc.GetProperties();
                string jsonRecord = JsonConvert.SerializeObject(record);
                CustomerOrders custOrders = JsonConvert.DeserializeObject&lt;CustomerOrders&gt;(jsonRecord);
                custOrders.id = doc.Id;
                custOrders.score = doc.Score;
                customerOrdersList.Add(custOrders);
            }
            return customerOrdersList;
        }</code></pre><figcaption><strong>Filtering/Limiting Search Results</strong></figcaption></figure><p>The query above filters the query to return only results where the <em>extendedPrice </em>is between 160 and 350. &#xA0;In addition to <em>numeric </em>filters, RediSearch also supports GEO filters; this is not covered here.<br>In line 12 we limit the returned records to the first 5. &#xA0;The parameters for the <em>Limit </em>are the <em>0ffset </em>and <em>number of records</em> &#xA0;&#x2013; the default is Limit(0,10).</p><p><strong>Restricting Search Within Fields</strong></p><p>The default behavior of RediSearch is to search all text properties specified in the schema. &#xA0;You can restrict which fields to search as shown in the code fragment below.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">         //search the customerID field for the text VINET 
          string searchString = &quot;(@customerID:VINET)&quot;; 
          
          //search the customerName field for wild-card text Supr&#xEA;*
          string searchString = &quot;(@customerName:Supr&#xEA;*)&quot;;       
          
          //search for the above 2 texts.  -- this is a UNION operation
          string searchString = &quot;(@customerName:Supr&#xEA;*) | (@customerID:VINET)&quot;;   </code></pre><figcaption><strong>Other Search Queries</strong></figcaption></figure><p>RediSearch has several query examples at <a href="https://oss.redislabs.com/redisearch/Query_Syntax/">this link</a>.</p>]]></content:encoded></item><item><title><![CDATA[Returning Specific Fields]]></title><description><![CDATA[You can make your queries more efficient by restricting the fields  that are returned from your RediSearch queries. The example(s) in this article shows how to use  the NRediSearch client to limit your results to specific fields.]]></description><link>https://blog.alumdb.com/returning-specific-fields/</link><guid isPermaLink="false">61eb3723a801321df2042976</guid><category><![CDATA[RediSearch]]></category><category><![CDATA[NRediSearch]]></category><dc:creator><![CDATA[Sam Dzirasa]]></dc:creator><pubDate>Fri, 14 Aug 2020 00:44:00 GMT</pubDate><media:content url="https://blog.alumdb.com/content/images/2020/08/NarrowYourSearch_01.png" medium="image"/><content:encoded><![CDATA[<h3 id="returning-specific-fields-narrow-your-search-">Returning Specific Fields (Narrow Your Search)</h3><img src="https://blog.alumdb.com/content/images/2020/08/NarrowYourSearch_01.png" alt="Returning Specific Fields"><p>Redis can have a large number of elements in a RediSearch document. &#xA0;You can limit your search queries to return only specific elements. &#xA0;(<em>This is similar to the SQL Select col1, col2, col3 ... from</em>.. &#xA0; &#xA0; statement). &#xA0;<br><br>In this example, we modify the code to return only the following fields:<br><br> &#xA0; 	<em>customerID, customerName, &#xA0;address, &#xA0;orderID, &#xA0;productName</em></p><p>In this code fragment, we have hard-coded items that you will normally pass in as parameters.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp"> public class DynamicDoc
{
    public string id { get; set; }
    public double score { get; set; }
    public Dictionary&lt;string, object&gt; payload{ get; set; }
}
 //**************************************************************      
        [HttpGet]
        [Route(&quot;api/search/orders/restrictfields&quot;)]
            public async Task&lt;List&lt;DynamicDoc&gt;&gt; SearchOrdersRestrictFields()
        {
            string searchString = &quot;Queso&quot;;
            IDatabase db = _connectionMultiplexer.GetDatabase();

            NRediSearch.Query query = new NRediSearch.Query(searchString);

            string[] returnFields = { &quot;customerID&quot;, &quot;customerName&quot;,&quot;address&quot;, &quot;orderID&quot;, &quot;productName&quot; };
           
            query.ReturnFields(returnFields);

            query.WithPayloads = true;

            SearchResult res = await _rediSearchClient.SearchAsync(query);

            List&lt;DynamicDoc&gt; dynamicDocs = new List&lt;DynamicDoc&gt;();

            foreach (Document doc in res.Documents)
            {
                IEnumerable&lt;KeyValuePair&lt;string, RedisValue&gt;&gt; record = doc.GetProperties();
                string jsonRecord = JsonConvert.SerializeObject(record);
                Dictionary&lt;string, object&gt; _record = JsonConvert.DeserializeObject&lt;Dictionary&lt;string, object&gt;&gt;(jsonRecord);
 
                DynamicDoc dynamicDoc = new DynamicDoc()
                {
                     id = doc.Id,
                     score = doc.Score,
                     payload = _record
                };
                dynamicDocs.Add(dynamicDoc);
            }
            return dynamicDocs;
        }</code></pre><figcaption><strong>Returning Specific Fields</strong></figcaption></figure><p> &#xA0; &#xA0; &#xA0; &#xA0; &#xA0;</p><!--kg-card-begin: markdown--><p><strong>Notes on the above code</strong></p>
<ol>
<li>We create a class <em>DynamicDoc</em> to hold the returned record(s)</li>
<li>Lines 17-19 specify the fields to return and add it to the query.</li>
<li>The <em>SearchResults</em> are NOT cast to the <em>CustomerOrders</em> class used in the previous examples.  Doing this could result in a significantly large number of unrequested null properties being returned to the caller.  Instead, we cast the document record (Payload) to a Dictionary&lt;string,object&gt;</li>
</ol>
<!--kg-card-end: markdown--><p>The returned records show only the requested fields in the payload; it looks like this; </p><figure class="kg-card kg-code-card"><pre><code class="language-json">[
    {
        &quot;id&quot;: &quot;northwind:orders_temp2147&quot;,
        &quot;score&quot;: 1,
        &quot;payload&quot;: {
            &quot;customerID&quot;: &quot;RATTC&quot;,
            &quot;customerName&quot;: &quot;Rattlesnake Canyon Grocery&quot;,
            &quot;address&quot;: &quot;2817 Milton Dr.&quot;,
            &quot;orderID&quot;: &quot;11077&quot;,
            &quot;productName&quot;: &quot;Queso Manchego La Pastora&quot;
        }
    },
    {
        &quot;id&quot;: &quot;northwind:orders_temp2131&quot;,
        &quot;score&quot;: 1,
        &quot;payload&quot;: {
            &quot;customerID&quot;: &quot;PERIC&quot;,
            &quot;customerName&quot;: &quot;Pericles Comidas cl&#xE1;sicas&quot;,
            &quot;address&quot;: &quot;Calle Dr. Jorge Cash 321&quot;,
            &quot;orderID&quot;: &quot;11073&quot;,
            &quot;productName&quot;: &quot;Queso Cabrales&quot;
        }
    }
]
    </code></pre><figcaption><strong>Sample Restricted Results (showing 2 records)</strong></figcaption></figure><p>The above query will return results for all the documents that match the the query. &#xA0;To restrict the query to specific documents keys we then make the following sample modifications to the code:</p><pre><code class="language-csharp">	string[] returnFields = { &quot;customerID&quot;, &quot;customerName&quot;,&quot;address&quot;, &quot;orderID&quot;, &quot;productName&quot; };
	string[] limitKeys = { &quot;northwind:orders_temp2063&quot;, &quot;northwind:orders_temp2147&quot; };
           
	query.LimitKeys(limitKeys);
	query.ReturnFields(returnFields);

	query.WithPayloads = true;</code></pre><!--kg-card-begin: markdown--><p><strong>Notes on above</strong></p>
<ol>
<li>lines 2-4 are added to specify the document keys (<em>ids</em>)</li>
<li>This query is then the equivalent of the SQL:</li>
</ol>
<p><strong>SELECT</strong> customerID, customerName, address, orderID, productName from <em>tablename</em><br>
where documentKey <strong>IN</strong> (&apos;northwind:orders_temp2063&apos;, &apos;northwind:orders_temp2147&apos;)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Processing Search Results]]></title><description><![CDATA[This blog covers how to process results from RediSearch using the .NET Core client for RediSearch (NRediSearch).  It simplifies the approach and generalizes it by using Generics .]]></description><link>https://blog.alumdb.com/processing-search-results/</link><guid isPermaLink="false">61eb3723a801321df2042977</guid><category><![CDATA[RediSearch]]></category><category><![CDATA[NRediSearch]]></category><dc:creator><![CDATA[Sam Dzirasa]]></dc:creator><pubDate>Thu, 13 Aug 2020 17:18:52 GMT</pubDate><media:content url="https://blog.alumdb.com/content/images/2020/08/Generics_01_01.png" medium="image"/><content:encoded><![CDATA[<h3 id="processing-results-for-multiple-poco-objects">Processing Results for multiple POCO objects</h3><img src="https://blog.alumdb.com/content/images/2020/08/Generics_01_01.png" alt="Processing Search Results"><p><br>The code fragment below shows how we processed the search results in earlier samples.</p><pre><code class="language-csharp">         foreach (Document doc in res.Documents)
            {           
                CustomerOrders _order = new CustomerOrders()
                {
                    id = doc.Id,
                    score = doc.Score,
                    customerName = (string)doc[&quot;customerName&quot;],
                    address = (string)doc[&quot;address&quot;],
                    customerID = (string)doc[&quot;customerID&quot;],
                    productName = (string)doc[&quot;productName&quot;],
                    extendedPrice = Convert.ToDecimal(doc[&quot;extendedPrice&quot;]),
                    //.. etc
                };
                customerOrdersList.Add(_order);   
            }
            return customerOrdersList;</code></pre><p>The disadvantage of this approach is that it is bound to the <em>CustomerOrders </em>class. &#xA0;If you have several classes to which you cast your search results, you&apos;ll have to repeat the same kind of code for every one of those classes.</p><p>We can improve the code to handle multiple classes by using a generic method to process/cast the results. &#xA0;The trick is let the POCO classes inherit from the same base as shown below for the <em>CustomerOrders </em>class.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">public class DocCommon
    {
        public string id { get; set; }
        public double score { get; set; }
    }
     public class CustomerOrders : DocCommon
   {
        public string customerID { get; set; }
        public string customerName { get; set; }
        public string address { get; set; }
        
        //etc..
   }</code></pre><figcaption><strong>CustomerOrders fragment</strong></figcaption></figure><p>Armed with this definition, we can then use the sample code below to call a generic method to cast the search results.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        [HttpGet]
        [Route(&quot;api/search/orders/redis&quot;)]
        public async Task&lt;List&lt;CustomerOrders&gt;&gt; SearchRedisOrdersData()
        {
            searchString = &quot;Vins&quot;;
            IDatabase db =   _connectionMultiplexer.GetDatabase();

            NRediSearch.Query query = new NRediSearch.Query(searchString);
            query.WithPayloads = true;

            SearchResult res = await _rediSearchClient.SearchAsync(query);
            
            List&lt;CustomerOrders&gt; appDocs = (from doc in res.Documents
                  select GetDocData&lt;CustomerOrders&gt;(doc)).ToList();            
            return appDocs;
        }</code></pre><figcaption><strong>Using Generic Method to Process Results</strong></figcaption></figure><p>The magic happens in lines #13-14, where we use the generic method <em>GetDocData </em>shown below to cast the search results.</p><p> Note: the generic method is constrained to <em>DocCommon</em>.</p><figure class="kg-card kg-code-card"><pre><code class="language-csharp">        public T GetDocData&lt;T&gt;(Document document) where T: DocCommon    
        {
            IEnumerable&lt;KeyValuePair&lt;string, RedisValue&gt;&gt; docRecords = document.GetProperties();
            
            string jsonDocRecord = JsonConvert.SerializeObject(docRecords);
            T record = JsonConvert.DeserializeObject&lt;T&gt;(jsonDocRecord);
            record.id = document.Id;
            record.score = document.Score;
            return record;
        }</code></pre><figcaption><strong>--Generic Method for Processing RediSearch Results</strong></figcaption></figure>]]></content:encoded></item></channel></rss>