Create a Command Line Application using Dotnet 6 | CLI Tool using Dotnet 6 | Dotnet 6 Console Application into CLI tool

{tocify} $title={Table of Contents}

Creating a Command Line Tool steps


1. Open Visual Studio 2022

2. Click on Create a New Project


3. Select Console App


4. Provide the folder and Solution Name


5. Select .Net 6.0 as dotnet version



6. As part of dotnet 6 you will have only Program.cs - startup.cs is merged along with Program.cs

Starting with .NET 6, the project template for new C# console apps generates the following code in the Program.cs file:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
The new output uses recent C# features that simplify the code you need to write for a program. For .NET 5 and earlier versions, the console app template generates the following code:

using System;

namespace MyApp // Note: actual namespace depends on the project name.
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}


 These two forms represent the same program. Both are valid with C# 10.0. When you use the newer version, you only need to write the body of the Main method. The compiler synthesizes a Program class with a Main method and places all your top level statements in that Main method. You don't need to include the other program elements, the compiler generates them for you. You can learn more about the code the compiler generates when you use top level statements in the article on top level statements in the C# Guide's fundamentals section.


You have two options to work with tutorials that haven't been updated to use .NET 6+ templates:

  • Use the new program style, adding new top-level statements as you add features.
  • Convert the new program style to the older style, with a Program class and a Main method.

Use the new program style

The features that make the new program simpler are top-level statements, global using directives, and implicit using directives.

The term top-level statements means the compiler generates the class and method elements for your main program. The compiler generated class and Main method are declared in the global namespace. You can look at the code for the new application and imagine that it contains the statements inside the Main method generated by earlier templates, but in the global namespace.

You can add more statements to the program, just like you can add more statements to your Main method in the traditional style. You can access args (command-line arguments), use await, and set the exit code. You can even add functions. They're created as local functions nested inside the generated Main method. Local functions can't include any access modifiers (for example, public or protected).

Both top-level statements and implicit using directives simplify the code that makes up your application. To follow an existing tutorial, add any new statements to the Program.cs file generated by the template. You can imagine that the statements you write are between the open and closing braces in the Main method in the instructions of the tutorial.

If you'd prefer to use the older format, you can copy the code from the second example in this article, and continue the tutorial as before.

Implicit using directives

The term implicit using directives means the compiler automatically adds a set of using directives based on the project type. For console applications, the following directives are implicitly included in the application:

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Other application types include more namespaces that are common for those application types.

If you need using directives that aren't implicitly included, you can add them to the .cs file that contains top-level statements or to other .cs files. For using directives that you need in all of the .cs files in an application, use global using directives.

Disable implicit using directives

If you want to remove this behavior and manually control all namespaces in your project, add <ImplicitUsings>disable</ImplicitUsings> to your project file in the <PropertyGroup> element, as shown in the following example:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    ...
    <ImplicitUsings>disable</ImplicitUsings>
  </PropertyGroup>

</Project>


 Global using directives

A global using directive imports a namespace for your whole application instead of a single file. These global directives can be added either by adding a <Using> item to the project file, or by adding the global using directive to a code file.

You can also add a <Using> item with a Remove attribute to your project file to remove a specific implicit using directive. For example, if the implicit using directives feature is turned on with <ImplicitUsings>enable</ImplicitUsings>, adding the following <Using> item removes the System.Net.Http namespace from those that are implicitly imported:

<ItemGroup>
  <Using Remove="System.Net.Http" />
</ItemGroup>


Command Line Tool Steps Continued

Add reference to this namespace:
using Microsoft.Extensions.CommandLineUtils;
Create a Class:

    public class CreateCommand : CommandLineApplication

Create Constructor:

Name of command line tool
Description of CLI

                this.Name = "Create";
                this.Description = "Create Name";

 Parameters of CLI 

parameters will be created using the CommandOption like below:

                CommandOption apiKey = this.Option("--api-key <apiKey>", "Api key", CommandOptionType.SingleValue);

 Now the overall Constructor code looks like below:

        public CreateCommand()
        {
            try
            {
                this.Name = "Create";
                this.Description = "Create Name";

                CommandOption apiKey = this.Option("--api-key <apiKey>", "Api key", CommandOptionType.SingleValue);

                CommandOption outputfilepath = this.Option("--output-file-path <outputfilepath>", "outputfilepath", CommandOptionType.SingleValue);

                this.OnExecute(async () =>
                {
                    string apiKeysStr = apiKey?.Value();
                    string outputfilepathStr = outputfilepath?.Value();

                    File.WriteAllText(outputfilepathStr, "test output");

                    return 0;
                });
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

Call the CreateCommand in Program.cs

create the app in Program.cs using CommandLineApplication class object with CLI name, fullname and description like below:
        var app = new CommandLineApplication()
        {
            Name = "YoutubeCLI",
            FullName = "YoutubeCLI",
            Description = "YoutubeCLI"
        };
Add our custom CreateCommand Class which is implementation of CommandLineApplication we did above:

        app.Commands.Add(new CreateCommand());

Below code will throw error if no commands specified as args:

        app.OnExecute(() => {
            ColoredConsole.Error.WriteLine("No commands specified, please specify a command");
            app.ShowHelp();
            return 1;
        });

 Below code will execute the command when called with arguments:

        return app.Execute(args);

 

 Now overall Program.cs code looks like this:

 // See https://aka.ms/new-console-template for more information


using Colors.Net;

using Microsoft.Extensions.CommandLineUtils;


Console.WriteLine("Hello, World!");

    try

    {

        var app = new CommandLineApplication()

        {

            Name = "YoutubeCLI",

            FullName = "YoutubeCLI",

            Description = "YoutubeCLI"

        };

        app.Commands.Add(new CreateCommand());

        app.OnExecute(() => {

            ColoredConsole.Error.WriteLine("No commands specified, please specify a command");

            app.ShowHelp();

            return 1;

        });

        return app.Execute(args);

    }

    catch (Exception e)

    {

        ColoredConsole.Error.WriteLine(e.Message);

        return 1;

    } 


Building YouTube Channel Videos fetcher CLI tool:

We will customize the CLI tool we built above to create a realtime YouTube Channel Videos Fetcher CLI tool by using YouTube apis:


Our Final CreateCommand class will look like this:

using Colors.Net;

using Microsoft.Extensions.CommandLineUtils;

using Newtonsoft.Json;


namespace YoutubeVideosCLI

{

    public class CreateCommand : CommandLineApplication

    {

        private string youtubeApiUrl = string.Empty;

        private string youtubeChannelsApiUrl = string.Empty;

        private string youtubeSearchApiUrl = string.Empty;

        private string requestParametersChannelId = string.Empty;

        private string requestChannelVideosInfo = string.Empty;

        private string videoDetailsUrl = string.Empty;

        private string playlistsUrl = string.Empty;

        private string playlistItemsUrl = string.Empty;


        public CreateCommand()

        {

            try

            {

                this.Name = "Create";

                this.Description = "Create Name";


                CommandOption apiKey = this.Option("--api-key <apiKey>", "Api key", CommandOptionType.SingleValue);


                CommandOption channelName = this.Option("--channelName <channelName>", "channel Name", CommandOptionType.SingleValue);


                CommandOption channel = this.Option("--channel <channel>", "channel Id", CommandOptionType.SingleValue);


                CommandOption inputfilepath = this.Option("--input-file-path <inputfilepath>", "inputfilepath", CommandOptionType.SingleValue);


                CommandOption outputfilepath = this.Option("--output-file-path <outputfilepath>", "outputfilepath", CommandOptionType.SingleValue);


                CommandOption videoNodeInPortal = this.Option("--videoNodeInPortal <videoNodeInPortal>", "videoNodeInPortal", CommandOptionType.SingleValue);


                CommandOption title = this.Option("--title <title>", "title Name", CommandOptionType.SingleValue);


                CommandOption datefrom = this.Option("--date-from <datefrom>", "datefrom", CommandOptionType.SingleValue);


                CommandOption dateto = this.Option("--date-to <dateto>", "dateto Name", CommandOptionType.SingleValue);


                CommandOption interval = this.Option("--interval <interval>", "interval Name", CommandOptionType.SingleValue);


                this.OnExecute(async () =>

                {

                    string apiKeysStr = apiKey?.Value();

                    string channelStr = channel?.Value();

                    string outputfilepathStr = outputfilepath?.Value();

                    string inputfilepathStr = inputfilepath?.Value();

                    string titleStr = title?.Value();

                    string channelNameStr = channelName?.Value();

                    string datefromStr = datefrom?.Value();

                    string datetoStr = dateto?.Value();

                    string intervalStr = interval?.Value();

                    int videoNodeInPortalInt = videoNodeInPortal.Value() == null ? 1 : int.Parse(videoNodeInPortal.Value());


                    youtubeApiUrl = "https://youtube.googleapis.com/youtube/v3/";

                    playlistsUrl = youtubeApiUrl + "playlists?part=snippet%2CcontentDetails&key={0}";

                    playlistsUrl = string.Format(playlistsUrl, apiKeysStr) + "&channelId={0}&maxResults=25&pageToken={1}";

                    playlistItemsUrl = youtubeApiUrl + "playlistItems?part=snippet%2CcontentDetails&key={0}";

                    playlistItemsUrl = string.Format(playlistItemsUrl, apiKeysStr) + "&playlistId={0}&maxResults=25&pageToken={1}";

                    youtubeChannelsApiUrl = youtubeApiUrl + "channels?key={0}&";

                    youtubeChannelsApiUrl = string.Format(youtubeChannelsApiUrl, apiKeysStr);

                    youtubeSearchApiUrl = youtubeApiUrl + "search?key={0}&";

                    youtubeSearchApiUrl = string.Format(youtubeSearchApiUrl, apiKeysStr);

                    requestParametersChannelId = youtubeChannelsApiUrl + "id={0}&part=id";

                    requestChannelVideosInfo = youtubeSearchApiUrl + "channelId={0}&part=id&order=date&type=video&publishedBefore={1}&publishedAfter={2}&pageToken={3}&maxResults=50";

                    videoDetailsUrl = youtubeApiUrl + "videos?part=snippet%2CcontentDetails%2Cstatistics&id={0}&key=";

                    videoDetailsUrl = string.Format(videoDetailsUrl, apiKeysStr);


                    Paperbits paperbitObj = new Paperbits();

                    string channelId = this.getChannelId(channelStr);

                    List<PlayListItem> playListItems = getChannelPlaylists(channelId);


                    using (StreamReader r = new StreamReader(inputfilepathStr))

                    {

                        string json = r.ReadToEnd();


                        // update channel name.

                        if (!string.IsNullOrEmpty(channelNameStr))

                        {

                            json = json.Replace("#ChannelName", channelNameStr);

                        }


                        paperbitObj = JsonConvert.DeserializeObject<Paperbits>(json);

                        Page pageVid = paperbitObj.pages.Where(p => p.Value.locales.EnUs.title.ToLower() == titleStr.ToLower())?.Select(pg => pg.Value)?.FirstOrDefault();

                        string fileContentKey = pageVid.locales.EnUs.contentKey.Replace("files/", "");

                        Page file = paperbitObj.files[fileContentKey];

                        string nodeStr = JsonConvert.SerializeObject(file.nodes[videoNodeInPortalInt]);

                        string nodeFileBackup = JsonConvert.SerializeObject(file.nodes[videoNodeInPortalInt]);


                        string fileStr = JsonConvert.SerializeObject(paperbitObj.files[fileContentKey]);

                        string fileStrBackup = JsonConvert.SerializeObject(paperbitObj.files[fileContentKey]);



                        foreach (var item in playListItems)

                        {

                            if (!string.IsNullOrEmpty(item.snippet.title))

                            {

                                fileStr = fileStr.Replace("#PlayListTitle", item.snippet.title);

                            }


                            fileStr = fileStr.Replace("#PlaylistDesc", item.snippet.description);


                            Page fileval = JsonConvert.DeserializeObject<Page>(fileStr);

                            List<VideoItem> videoItems = getVideosFromPlaylists(item.id);

                            int count = 0;

                            int pageCount = 0;


                            foreach (var videoItem in videoItems)

                            {



                                Node node = new Node();

                                count++;

                                if (!string.IsNullOrEmpty(videoItem.snippet.title))

                                {

                                    nodeStr = nodeStr.Replace("#VidoeTitle1", videoItem.snippet.title);

                                }

                                if (!string.IsNullOrEmpty(videoItem.snippet.description))

                                {

                                    nodeStr = nodeStr.Replace("#VideoDescription1", videoItem.snippet.description);

                                }

                                if (!string.IsNullOrEmpty(videoItem.id))

                                {

                                    nodeStr = nodeStr.Replace("VideoId1", videoItem.snippet.resourceId.videoId);

                                }

                                node = JsonConvert.DeserializeObject<Node>(nodeStr);

                                nodeStr = nodeFileBackup;


                                if (count == 1)

                                {

                                    fileval.nodes[videoNodeInPortalInt] = node;

                                }

                                else

                                {

                                    fileval.nodes.Add(node);

                                }

                                if (count == 5)

                                {

                                    count = 0;

                                    pageCount++;

                                    AddPageToModel(paperbitObj, item, item.id + pageCount, fileval, pageCount.ToString());

                                    fileStr = fileStrBackup;

                                    if (!string.IsNullOrEmpty(item.snippet.title))

                                    {

                                        fileStr = fileStr.Replace("#PlayListTitle", item.snippet.title);

                                    }


                                    fileStr = fileStr.Replace("#PlaylistDesc", item.snippet.description);


                                }

                            }


                            if (paperbitObj.files.ContainsKey(item.id))

                            {

                                paperbitObj.files.Remove(item.id);

                            }

                            AddPageToModel(paperbitObj, item, item.id, fileval);

                            fileStr = fileStrBackup;


                            fileval = JsonConvert.DeserializeObject<Page>(fileStr);

                        }

                    }


                    string outJson = JsonConvert.SerializeObject(paperbitObj, Formatting.Indented, new JsonSerializerSettings

                    {

                        NullValueHandling = NullValueHandling.Ignore

                    });

                    File.WriteAllText(outputfilepathStr, outJson);


                    return 0;

                });

            }

            catch (Exception ex)

            {

                throw ex;

            }

        }


        private static void AddPageToModel(Paperbits paperbitObj, PlayListItem item, string pageName, Page fileval, string pageNum = "")

        {

            if (paperbitObj.files.ContainsKey(pageName))

            {

                paperbitObj.files[pageName] = fileval;

            }

            else

            {

                paperbitObj.files.Add(pageName, fileval);

            }


            Page newPage = new Page();

            string pageid = pageName + "_page";

            newPage.key = "pages/" + pageid;

            newPage.locales = new Locales();

            newPage.locales.EnUs = new EnUs();

            newPage.locales.EnUs.contentKey = "files/" + item.id;

            newPage.locales.EnUs.title = item.snippet.title + "-" + pageNum;

            newPage.locales.EnUs.description = item.snippet.description;

            newPage.locales.EnUs.permalink = "/videos-" + pageid;


            if (paperbitObj.pages.ContainsKey(pageid))

            {

                paperbitObj.pages.Remove(pageid);

            }

            paperbitObj.pages.Add(pageid, newPage);


            NavigationItem navigationItem = paperbitObj.navigationItems.Find(n => n.key == "navigationItemPlaylist");


            if (navigationItem == null)

            {

                navigationItem = new NavigationItem();

            }

            if (navigationItem.navigationItems == null)

            {

                navigationItem.navigationItems = new List<NavigationItem>();

            }


            //add menu navigations items based on pages created.

            navigationItem.navigationItems.Add(new NavigationItem

            {

                key = pageid + "_nav",

                label = newPage.locales.EnUs.title,

                targetWindow = "_self",

                anchor = "5FbTd",

                targetKey = newPage.key

            });

            int index = paperbitObj.navigationItems.FindIndex(n => n.key == "navigationItemPlaylist");

            paperbitObj.navigationItems[index] = navigationItem;

        }


        private string getChannelId(string channelName)

        {

            ColoredConsole.Out.WriteLine("Searching channel id for channel: " + channelName);


            try

            {

                string url = string.Format(requestParametersChannelId, channelName);


                HttpClient client = new HttpClient();


                string response = client.GetStringAsync(url).Result;


                if (response != string.Empty)

                {

                    Playlist playlist = JsonConvert.DeserializeObject<Playlist>(response);

                    return playlist.items[0].id;

                }

                else

                {

                    throw new Exception("channel id not found.");

                }

            }

            catch (Exception ex)

            {


                throw ex;

            }


        }


        private dynamic getVideoDetailsById(string videoId)

        {

            try

            {

                string url = string.Format(videoDetailsUrl, videoId);

                ColoredConsole.Out.WriteLine("Request: " + url);


                HttpClient client = new HttpClient();

                dynamic response = client.GetAsync(url).Result;


                if (response.items?.Count > 0)

                {

                    return response.items[0];

                }

                else

                {

                    throw new Exception("channel id not found.");

                }

            }

            catch (Exception ex)

            {


                throw ex;

            }

        }


        private List<PlayListItem> getChannelPlaylists(string channelId)

        {

            try

            {

                bool foundAll = false;

                List<PlayListItem> playlists = new List<PlayListItem>();

                string nextPageToken = string.Empty;


                while (!foundAll)

                {

                    string url = string.Format(playlistsUrl, channelId, nextPageToken);


                    ColoredConsole.Out.WriteLine("Request: " + url);


                    HttpClient client = new HttpClient();

                    string response = client.GetStringAsync(url).Result;


                    if (response != string.Empty)

                    {

                        Playlist playlist = JsonConvert.DeserializeObject<Playlist>(response);

                        playlist.items.ForEach(item => playlists.Add(item));


                        nextPageToken = playlist.nextPageToken;


                        if(nextPageToken == null || nextPageToken == string.Empty)

                        {

                            foundAll = true;

                        }

                    }

                    else

                    {

                        foundAll = true;

                        throw new Exception("channel id not found.");

                    }

                }

                return playlists;

            }

            catch (Exception ex)

            {


                throw ex;

            }

        }


        private List<VideoItem> getVideosFromPlaylists(string playlistId)

        {

            try

            {

                bool foundAll = false;

                List<VideoItem> playlists = new List<VideoItem>();

                string nextPageToken = string.Empty;


                while (!foundAll)

                {

                    string url = string.Format(playlistItemsUrl, playlistId, nextPageToken);


                    ColoredConsole.Out.WriteLine("Request: " + url);


                    HttpClient client = new HttpClient();

                    string response = client.GetStringAsync(url).Result;


                    if (response != string.Empty)

                    {

                        PlaylistItemVal playlist = JsonConvert.DeserializeObject<PlaylistItemVal>(response);

                        playlist.items.ForEach(item => playlists.Add(item));

                        nextPageToken = playlist.nextPageToken;


                        if (nextPageToken == null || nextPageToken == string.Empty)

                        {

                            foundAll = true;

                        }

                    }

                    else

                    {

                        foundAll = true;

                        throw new Exception("channel id not found.");

                    }

                }

                return playlists;

            }

            catch (Exception ex)

            {


                throw ex;

            }

        }

    }

}

Running this CLI tool locally:

dotnet run create --api-key secrets.GoogleApiKey --channel youTubeChannelId --output-file-path "outputfilepath.json" --input-file-path "inputfilepath.json" --title "videos" --channelName  "Sanjeevi Channel"


This command line tool writes to json file which can be used by the Paperbits generated static website to show videos from provided YouTube channel.


We can customize this CLI tool to write to any html or other files and which can be used to build a static website which will update itself using this CLI tool. 

You can refer to the Repo with full code here:

SSanjeevi/YouTubePlaylistSite: YouTube Playlist Site (github.com)


Will write an article on how to use this CLI tool in GitHub Actions.

Reference:

1 Comments

Previous Post Next Post

postad

post ad 2