c# – HoNoSoFt https://blog.honosoft.com Blog & Roll Tue, 09 Jul 2019 12:49:35 +0000 en-CA hourly 1 https://wordpress.org/?v=6.8.10 https://blog.honosoft.com/wp-content/uploads/2018/06/logo.png c# – HoNoSoFt https://blog.honosoft.com 32 32 Dotnet Core : What can you do in case you wish to authorize without [Authorize] ? https://blog.honosoft.com/2019/07/09/dotnet-core-what-can-you-do-in-case-you-wish-to-authorize-without-authorize/?utm_source=rss&utm_medium=rss&utm_campaign=dotnet-core-what-can-you-do-in-case-you-wish-to-authorize-without-authorize https://blog.honosoft.com/2019/07/09/dotnet-core-what-can-you-do-in-case-you-wish-to-authorize-without-authorize/#comments Tue, 09 Jul 2019 12:47:21 +0000 https://blog.honosoft.com/?p=586 Continue Reading]]> In some cases, the [Authorize] attribute is not all you want. Sometimes, you wish that something else existed and that’s where the IAuthorizationFilter (or IAsyncAuthorizationFilter) come into play. Before going there, ensure that you don’t simply need a new Policy (See MSDN article or doc.). For more details about the Filter I will let you read the MSDN page which is always updated.

What we will cover here:

Ordering of the filters within Dotnet Core

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.
Image from this MSDN article

Basically, most of the time we want to add some behavior within the ActionFilter area. But when it comes to authorization, the best is to stop the entire pipeline as soon as possible. That’s where the IAuthorizationFilter comes into play.

Sample of creating an IAuthorizationFilter

The IAuthorizationFilter only require us one method to implement (i.e.: OnAuthorization). There is also no need of doing any dependency injection for such attribute in order to activate it. The following code is only an example doing nothing special, but you should grasp the idea.

    public sealed class SpecialAuthorizeAttribute : Attribute, IAuthorizationFilter  // or IAsyncAuthorizationFilter  ...
    {
        private const string HEADER_SPECIAL_STUFF = "specialHeaderStuff";

        public void OnAuthorization(AuthorizationFilterContext context)
        {
            if (context != null)
            {
                var authCode = context.HttpContext.Request.Headers[HEADER_SPECIAL_STUFF];
                // Write your logic here.
                if (string.IsNullOrEmpty(authCode))
                {
                    context.Result = new UnauthorizedObjectResult($"{HEADER_SPECIAL_STUFF} is required");
                    return;
                }

                if (!authCode.Equals("IAmValid"))
                {
                    context.Result = new UnauthorizedObjectResult($"{HEADER_SPECIAL_STUFF} is invalid");
                    return;
                }

                // ===== if you're here, it's a success =====
                // DO STUFF
                //
                // ===== you can also use registered services =====
                // var someService = context.HttpContext.RequestServices.GetService<ISomeService>();
            }
        }
    }

Sample of using our custom IAuthorizationFilter

The following code is based on a new WebApi project.

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{

    // GET api/values/5
    [SpecialAuthorize]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }

    // ... other stuff
}

In order to execute, you can simply do a HTTP call using Curl.

nordes@Something:~$ curl https://localhost:5001/api/values/1 --insecure && echo ''
specialHeaderStuff is required
nordes@Something:~$ curl https://localhost:5001/api/values/1 -H 'specialHeaderStuff:IAmInvalid' --insecure && echo ''
specialHeaderStuff is invalid
nordes@Something:~$ curl https://localhost:5001/api/values/1 -H 'specialHeaderStuff:IAmValid' --insecure && echo ''
value

As you can see, when I use a Valid value, it is accepted and then process the Controller-Action. The attribute could also be put on the Controller and in such case will apply that rule to every actions.

Conclusion

As you can see, it’s not something complex to do. What you should ask yourself is if you need to use it? In my case, I had to use it for specific scenario, but most people will probably simply use the policy over the official Authorize attribute given by the framework.

]]>
https://blog.honosoft.com/2019/07/09/dotnet-core-what-can-you-do-in-case-you-wish-to-authorize-without-authorize/feed/ 1
#Slack |> App |> Smee |> My Local App |> #Slack Reply : How to interact when you’re behind Firewall/Proxy https://blog.honosoft.com/2019/05/24/slack-app-smee-my-local-app-slack-reply-how-to-interact-when-youre-behind-firewall-proxy/?utm_source=rss&utm_medium=rss&utm_campaign=slack-app-smee-my-local-app-slack-reply-how-to-interact-when-youre-behind-firewall-proxy https://blog.honosoft.com/2019/05/24/slack-app-smee-my-local-app-slack-reply-how-to-interact-when-youre-behind-firewall-proxy/#respond Fri, 24 May 2019 12:48:27 +0000 https://blog.honosoft.com/?p=562 Continue Reading]]> Introduction

All I wanted to do is simply have a way to connect to my local server on the network and then when receiving a specific request from #Slack. Once received act on it, do a some background work or send reports. This server is obviously not exposed on the internet and reside behind a firewall/proxy. For that reason, I had to be a bit more creative and think out of the box.

In the past I resolved this by using a WebSocket to an Heroku WebSite. It works but it was not perfect. So, I started to look around for a better way to stream a service where I a simple Webhook would be forwarded. In that manner, I could watch a service where it send me back the data. I recently came across Smee.io which use the Server Side Events (SSE) but as a streaming service on the HTTP port. Following is a story of how to make all that working together.

Before going further, I would have loved a solution like
ngrok, but it’s not possible behind a proxy/firewall such as where I work.

Step 1. Grab a new Smee URL

To get the URI, it’s deadly simple. Simply go on Smee.io and then click on create the big button in order to create a new Webhook. Keep the Webhook URL for the next step.

Step 2. Configure the #Slack App/Bot

In that App, we will setup a bot, a hook and a bot for commands.

Configure the App

Open your #Slack page and add an App and if you don’t know how, simply go on https://api.slack.com/ and click on Start Building button and fill the form. It should look like this:

Once you have created your app, you will need to enable “Bot” and give him a name, and then go into the Create a new command page and then add a new command like the following:

Install your app

For that, you go for example in OAuth & Permissions and click install app To Workspace. (Note, you might also have to add a permission File.Upload.User)

You can see the beginning of the token. The 4 first characters indicate what kind of token it is. (Documentation in slack documentation exists for more details)

Step 3. Keep it simple with a sample app in NodeJS or Dotnet Core

Due to some missing implementation in the original repository/server, I’ve added what’s missing in my fork within GitHub. I then use my Heroku account to generate automatically my Docker container with the service. In the future, the issues should be fixed (PR currently waiting to be approved).

The main issue with the Smee.IO website is that it does not yet forward the content if it’s using x-www-form-urlencoded. If you use my branch, for now you will be ok. You can even use it for building your docker service on heroku.

Sample receiving data using NodeJS (index.js + smee-client & express packages)

const SmeeClient = require('smee-client')

/// Smee client (websocket)
const smee = new SmeeClient({
  source: 'https://smee.io/5sFasdasdukH5VQidvpGv', // In case Smee is not updated, please use my repository + an heroku deployment. It is that easy.
  target: 'http://localhost:3000/events',
  logger: console
})
const events = smee.start()
/// WebServer
const express = require('express');
const app = express();
const router = express.Router();

app.use(express.json()) // Instead of body-parser
app.use(express.urlencoded()) // Instead of body-parser
app.use(function(req, res, next) { // Allow CORS if you want.
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

console.log(app)

// from top level path e.g. localhost:3000, this response will be sent
app.get('/', (request, response) => response.send('Hello World'));
app.use('/events', router);

router.post('/', (request, response, next) => {
  console.log(request.body) // Raw data
  response.end();
});

app.listen(3000, () => console.log('Listening on port 3000'));

Sample receiving data using Dotnet Core

NuGet Package: Smee.IO.Client (That library simply do the SSE streaming for smee.io event types for you. So, if you want to use it for different SSE messages types, you will have to adapt the code).

The following code is also available in my Github repo (Demo).

using System;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace Smee.IO.Client.ConsoleDemo
{
    class Program
    {
        private static CancellationTokenSource source;

        static async Task Main(string[] args)
        {
            source = new CancellationTokenSource();
            var token = source.Token;

            // This could also be done automatically, but since we expect some developer to test, let's
            // keep it this way.
            Console.WriteLine("Hi there, please get a valid url on https://smee.io/new (or your own server. In case of this demo, I will use my own instance)");
            Console.Write("Please enter a valid URI because no validation will be made: ");
            var smeeUri = new Uri(Console.ReadLine());

            Console.ForegroundColor = ConsoleColor.DarkRed;
            Console.WriteLine(" > Hit CTRL-C in order to stop everything.");
            Console.WriteLine();
            Console.ResetColor();

            var smeeCli = new SmeeClient(smeeUri);
            smeeCli.OnConnect += (sender, a) => Console.WriteLine($"Connected to Smee.io ({smeeUri}){Environment.NewLine}");
            smeeCli.OnDisconnect += (sender, a) => Console.WriteLine($"Disconnected from Smee.io ({smeeUri}){Environment.NewLine}");
            smeeCli.OnMessage += (sender, smeeEvent) =>
            {
                Console.Write("Message received: ");
                Console.ForegroundColor = ConsoleColor.DarkYellow;
                Console.Write(JsonConvert.SerializeObject(smeeEvent)); // This is a typed object.
                Console.ResetColor();
                Console.WriteLine();
                Console.WriteLine();
            };
            smeeCli.OnPing += (sender, a) => Console.WriteLine($"Ping from Smee{Environment.NewLine}");
            smeeCli.OnError += (sender, e) => Console.WriteLine($"Error was raised (Disconnect/Anything else: {e.Message}{Environment.NewLine}");

            Console.CancelKeyPress += (sender, eventArgs) =>
            {
                source.Cancel();
                eventArgs.Cancel = true;
            };

            await smeeCli.StartAsync(token);
            Console.WriteLine("Finish executing. Thank you!");
        }
    }
}

Example of what you can see in Smee.io console

Slack Webhook call (Command)

The command in the previous capture is using a different command, don’t worry. Anything will do.

If the payload is empty, it’s because the content type is application/x-www-form-urlencoded (Current limitation on Smee.io). If you use my branch, as said earlier, you will have the proper data.

We can receive, now let’s send

For this part, you have a few option. I will be using the Dotnet implementation from now on. However, know that you can either use a library or use raw HTTP command using the Slack API. Basically, you will do either HTTP post to a specific URL or to the response_url. The response_url is not something that stays forever. As of now, it stays 30 minutes alive, after that, normally, it won’t be usable. It can be useful to make a small dialog with options that needs to be selected. Here we will now focus to send a simple message back to say “Message received!”.

In Dotnet, but also in NodeJS, some people already have written some libraries (e.g.: SlackAPI). It’s up to you to use them. The following code will be using a raw lazy HTTP post. For that, let’s replace smeeCli.OnMessage += ... by the following (not fully completed, but you get the idea):

smeeCli.OnMessage += (sender, smeeEvent) =>
{
    IncomingMessage msgBody = null;
    if (e.Data.Body != null)
    {
        msgBody = (e.Data.Body as JObject)?.ToObject<IncomingMessage>();
    }

    // If it's for command listener, you could use a strategy pattern.
    // Some code is not in previous block, but you can easily find out what to put in order to make
    // it work.
    _slackHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _slackConfiguration.BotOAuthToken);
    var msg = new
    {
        token = _slackConfiguration.BotOAuthToken,
        channel = msgBody.UserId,
        // response_type = "ephemeral", // See doc.
        text = "Message received!"
    };
    var content = new StringContent(JsonConvert.SerializeObject(msg), Encoding.UTF8, "application/json");
    var result = await _slackHttpClient.PostAsync("chat.postMessage", content).ConfigureAwait(false);
    // https://api.slack.com/methods/im.open/test (open)
    // send message to specific channel...
    // https://api.slack.com/methods/im.close/test (open)
    // Conversation could be created, but for now let's consider no conversation.
    // e.g.: https://api.slack.com/dialogs#select_elements <== create a select box...
};

What’s next?

  • Basically, if you wish, you could also link yourself with Luis.AI, TensorFlow or anything in order to make a Chat Bot more alive and more intelligent.
  • Create some command to interact with your servers
  • Link your slack account with some of your service in order to be notified when some jobs are completed for example.
  • Link/Sync/Send stuff between your #Slack and your MS Team
  • …the sky is the limit…

The limits are actually your limits and how secure you want your stuff to be (as usual).

Conclusion

I hope you’ve enjoyed that article ;). I don’t have much time recently, so it’s hard to write more often.

]]>
https://blog.honosoft.com/2019/05/24/slack-app-smee-my-local-app-slack-reply-how-to-interact-when-youre-behind-firewall-proxy/feed/ 0
Linux + Dotnet Core – GSettings (LibGio) directly from code https://blog.honosoft.com/2019/03/14/linux-dotnet-core-gsettings-libgio-directly-from-code/?utm_source=rss&utm_medium=rss&utm_campaign=linux-dotnet-core-gsettings-libgio-directly-from-code https://blog.honosoft.com/2019/03/14/linux-dotnet-core-gsettings-libgio-directly-from-code/#comments Thu, 14 Mar 2019 12:36:00 +0000 https://blog.honosoft.com/?p=518 Continue Reading]]> Some people like to execute shell commands for LibGio – GSettings using NodeJs (child_process) or Dotnet (Process). Personally, I prefer to use directly the C/C++ API from the compiled binaries. It helps to keep the performance and avoid spawning new process instances for no good reason.

In this article, we will talk about how to call the libgio (GSettings especially) from our Dotnet application. Obviously, this will work only from Linux having the library. People familiar with Linux/Gnome GUI already knows about the libgio by using C or C++, but what about using C#?

In case you want to know more about libgio, I recommend you to go on the Gnome Developer site and as extra resource you can also go on their blog post about the topic (First Step with GSettings). I will repeat myself later, but, the GSettings links configuration to your session using the DBUS session. If you don’t have any session, you will be able to read the default value, but not to modify them.

What are we going to experiment?

We will simply try to get the GSettings and set a new value. It means that we will be using only 3 API’s our of many. The full name of the library being used is libgio-2.0.so and those 3 API’s are:

As you can see, the GSettings is a database containing the key-value pairs of strongly typed documents. What is really under the hood for the schema is XML files. The XML is then compiled and the system will be using binary data instead of XML. The normal emplacement for the schemas is in /usr/share/glib-2.0/schemas. Some details about how to create your own schema (Python) can be found on that blog. In case you don’t want to have to specify the path on each request on your custom schema, simply update it by specifying the path directly in the schema element.

In the case you want to test using the command line, you can use the gsettings –help. In case you play in your subsystem (Windows10), you might want to install libglib2.0-bine and dconf-gsettings-backend.

Example of gsettings file for your application

The example bellow, is purely fictional, but if you wish you can look to existing schema available in /usr/share/glib-2.0/schemas/. If the path is part of the schema element, you won’t need to write the path at every request. Also note that the GSettings set won’t work if your dbus is not started for your user. The dbus bridge your settings with your current session.

<schemalist>
  <enum id="com.honosoft.sample.enum">
    <value nick="off" value="1"/>
    <value nick="warming" value="2"/>
    <value nick="on" value="3"/>
  </enum>

  <schema path="/usr/share/glib-2.0/schemas/" id="com.honosoft.sample">
 
    <key type="b" name="my-flag-is-active">
      <default>false</default>
      <summary>I am a boolean flag</summary>
      <description>A description of your boolean</description>
    </key>
    <key type="i" name="threshold">
        <range min="1" max="100"/>
        <default>50</default>
        <summary>Some threshold</summary>
        <description>Some threshold from 1 to 100 about something</description>
    </key>
 
    <key name="list-prime-numbers" type="ai">
      <default>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43]</default>
      <summary>Some numbers</summary>
      <description>
        List of numbers
      </description>
    </key>
 
    <key name="list-my-pets" type="as">
        <default>['Captain', 'Scrooge', 'Mana', 'Saturn', 'Brutal']</default>
        <summary>Some array of strings</summary>
        <description>List my pet(s) name</description>
    </key>
 
    <key name="current-state" enum="com.honosoft.sample.enum">
        <default>'off'</default>
        <summary>display a state of my application</summary>
        <description>Using the enum allow only a few values pre-defined</description>
    </key>

    <child name="best-book" schema="com.honosoft.sample.book"/>
  </schema>
 
  <schema path="/usr/share/glib-2.0/schemas/" id="com.honosoft.sample.book">
      <key name="title" type="s">
          <default>'Clean Code'</default>
          <summary>Define the book title</summary>
      </key>
      <key name="author" type="s">
          <default>'Robert C. Martin'</default>
          <summary>Define the author name</summary>
      </key>
  </schema>
</schemalist>

After putting your file in the /usr/share/glib-2.0/schemas/ , you need to compile it. To compile and make it available, you have to execute glib-compile-schemas /usr/share/glib-2.0/schemas/ After, you will be able to access the key-value-pair. Note that the command exists simply for debugging purpose. More details can be found on that article.

Sample to retrieve an array of string

How do we call such library from C# (Dotnet)

It’s not as complex as you might think. There’s some gotcha, and if you have C/C++ knowledge it will help you to understand those (such as void pointers).

In Dotnet, we have the attribute DllImports which consists of doing the link between the “dll” (or compiled C/C++ .so) and the framework. The best practice for that would be to create a layer of abstraction over the layer mapping such commands. Here’s the 3 command that we will be using mapped in C# using the attribute:

[DllImport("libgio-2.0.so", EntryPoint = "g_settings_new")]
public static extern IntPtr New(string schema);

[DllImport("libgio-2.0.so", EntryPoint = "g_settings_get_int")]
public static extern int GetInt(IntPtr settings, string key);

[DllImport("libgio-2.0.so", EntryPoint = "g_settings_set_int")]
public static extern bool SetInt(IntPtr settings, string key, int value);

As you can see, the IntPtr is a reference to the GSettings schema.

Let’s experiment the whole process

  1. Request to the user what schema to retrieve
  2. Request to the user what key (int key) to retrieve from the schema
  3. Get the int value from the schema + key
  4. Request the user to give a new value
  5. Set or store the new value
class Program
{
    static void Main(string[] args)
    {
        Console.Write("Schema to look: ");
        var gsettingsSchema = Console.ReadLine();
        Console.Write("Key to look: ");
        var gsettingsKey = Console.ReadLine();

        var gsettings = DemoBindings.GSettings.New(gsettingsSchema);
        var result = DemoBindings.Bindings.GSettings.GetInt(gsettings, gsettingsKey);
        Console.WriteLine($"Value is: {result}");
        Console.Write("Please give me a new value to set: ");
        var newValue = Console.ReadLine();

        var intValue = int.Parse(newValue); // No validation, but you should ;)
        var success = DemoBindings.GSettings.SetInt(gsettings, gsettingsKey, intValue);
        Console.WriteLine($"The new value was set with {success ? string.Empty : "no "} success")
        Console.ReadKey();
    }
}

What does it mean for you?

In the eventually you would write an application that runs in the background triggering some event on the display, you could take advantage of the applications settings (listen even to those) and then maybe trigger some actions. For example:

  • Toggle the display of a virtual keyboard on a touch screen.
  • Get the current session details (windows)

I hope you’ve enjoyed, even if this is a really short blog.

]]>
https://blog.honosoft.com/2019/03/14/linux-dotnet-core-gsettings-libgio-directly-from-code/feed/ 2
Linux & Dotnet – Read from a device file https://blog.honosoft.com/2019/03/12/linux-dotnet-read-from-a-device-file/?utm_source=rss&utm_medium=rss&utm_campaign=linux-dotnet-read-from-a-device-file https://blog.honosoft.com/2019/03/12/linux-dotnet-read-from-a-device-file/#comments Tue, 12 Mar 2019 15:21:37 +0000 https://blog.honosoft.com/?p=491 Continue Reading]]> As you may know, Dotnet Core now runs within Linux. Let’s explore and play around enjoying the fact we can play in any environment now in Core.

Let’s consider that you have a touch panel where you would like to handle everything by yourself (touch, move, etc.). How in the world would you get the touch event?

Probably, if you never played around that low in the system, you probably don’t know. Interestingly, there’s the HID (Human Interface Devices). Most of the documentation is available if you search from your favorite engine. What you really want to know, is the binary data structure, once you have that, the fun can begin. In the case you can’t get any description anywhere, I guess the best choice is to do some reverse engineering (not recommended).

In this article we will only analyse a touch event generating a lot of binary data. I don’t want to cover the entire touch/move/etc of the binary data.

Let’s take the touch screen “touch” click event (KeyPress/Release)

Find the Screen Input Device

Within Linux system, there’s plenty of command in order to find your devices. The device files are usually located within the /dev/…. folder. In the past, we used to have all the existing device in the world in that folder. Nowadays, we simply have the bare minimum and that’s great. Most of the devices, if not all, are documented in the Linus repository. There’s many way to retrieve more details or at least the input device of your choice (touch panel).

One of those method consist in looking in all the USB (/proc/bus/usb/devices) or Input devices (/proc/bus/input/devices) in your system. The touch panel is usually linked to an “Event[1..5]” device.

An alternative is to configure your display to point towards your X-Server and then use the xinput command. If you fail to execute that command, it’s either not installed or you simply forgot to set your environment variable DISPLAY.

xinput --list

Second alternative. Without installing anything, you can use the device manager and look directly in the database and find your touch screen. The command for such a case is udevadm and of course you are required to run this as a root (sudo).

# Give all the devices available on your system. If you have a lot of
# devices connected, consider making the output redirected into a file.
# e.g.: udevadm info --export-db > myFile.txt
udevadm info --export-db 

An another alternative, just in case, is to simply look in the special folder “/dev/input/by-id” and “/dev/input/by-path“. The first give the currently connected device with input capabilities. So if you connect or disconnect an input device, you should be able to see it there. By doing a “ls -l” you will see which event file it’s using since it’s creating a symbolic link to it. For example in this output the event1 is my touchscreen:

myroot@mymachine:/dev/input/by-path# ls -l
total 0
lrwxrwxrwx 1 nobody nogroup 9 Mar  6 08:54 pci-0000:00:14.0-usb-0:13.1:1.1-event -> ../event1
lrwxrwxrwx 1 nobody nogroup 9 Mar  7 23:55 pci-0000:00:14.0-usb-0:13.4.2:1.0-event-kbd -> ../event2

Normally, from here, you know which device file correspond to your device. You might even have more details if you used the udevadm.

Read the binary content

Devices files are the raw data file. In order to be able to do something, you need to stream it as you would do for a TCP/UDP/File data. The structure of the content is structured following the driver rules. For example, the Keyboard and Mouse and a few other devices are considered as well known devices and are well documented all over the internet. You can even go directly in the Unix source code in order to see the .h file.

Let’s create a simple loop receiving the binary content while you touch the panel and then let’s output that raw data using an hex string.

public void ReadDeviceStream(CancellationToken stoppingToken) 
{
  // Use the device file
  var targetFile = new FileInfo("/dev/inputs/event1");

  // Open a stream
  using (FileStream fs = targetFile.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  {
    stoppingToken.Register(() => fs?.Close());
    int blockId = 1;

    // A big buffer, for simplicity purpose and to receive the entire touch report. We should use
    // the proper buffer size based on the event size. Note that we could also
    // use the binary reader
    var buffer = new byte[1024];

    // Read until the token gets cancelled
    while (!stoppingToken.IsCancellationRequested && fs.Read(buffer) > 0)
    {
      ShowBinaryContent(blockId, buffer);
      blockId++;
    }
  }
}

public void ShowBinaryContent(int blockId, byte[] buffer)
{
  Console.WriteLine($"Block #{blockId}");
  Console.WriteLine(BitConverter.ToString(bytes)); // Hex format: AB-1D...
  Console.WriteLine(string.Empty);
}

How to interpret the data

The result of the previous small program is displayed here

// Block #1
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-39-00-03-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-35-00-31-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-36-00-E8-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-01-00-4A-01-01-00-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-00-00-31-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-03-00-01-00-E8-01-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-04-00-05-00-00-00-00-00-
59-9A-74-5C-00-00-00-00-9C-C7-04-00-00-00-00-00-00-00-00-00-00-00-00-00

// Block #2
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-03-00-35-00-2C-01-00-00-
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-03-00-00-00-2C-01-00-00-
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-04-00-05-00-10-27-00-00-
59-9A-74-5C-00-00-00-00-6F-40-05-00-00-00-00-00-00-00-00-00-00-00-00-00
            
// Block #3
59-9A-74-5C-00-00-00-00-5D-96-05-00-00-00-00-00-04-00-05-00-20-4E-00-00-
59-9A-74-5C-00-00-00-00-5D-96-05-00-00-00-00-00-00-00-00-00-00-00-00-00

// Block #4
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-03-00-39-00-FF-FF-FF-FF-
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-01-00-4A-01-00-00-00-00-
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-04-00-05-00-30-75-00-00-
59-9A-74-5C-00-00-00-00-7D-9A-05-00-00-00-00-00-00-00-00-00-00-00-00-00

As you can see, we receive many events at the same time and each block contains their own timestamp (few first bytes on each lines). A block here contains more than one instruction. For example, it tells you where you pushed and if it was a push event or release. Let’s focus on the data looking like the Block #1.

public static void Main()
	{
		// Release
		 var binaryStrings = 
		 new [] { "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-39-00-0C-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-35-00-51-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-36-00-93-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-01-00-4A-01-01-00-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-00-00-51-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-03-00-01-00-93-01-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-04-00-05-00-00-00-00-00",
                "E3-00-75-5C-00-00-00-00-3F-72-09-00-00-00-00-00-00-00-00-00-00-00-00-00"};

		foreach (var bs in binaryStrings) {
			byte[] data = bs.Split('-').Select(b => Convert.ToByte(b, 16)).ToArray();

			Console.Write($"[Sec: {BitConverter.ToUInt32(data, 0)}]");
			Console.Write($"\t[USec: {BitConverter.ToUInt32(data, 8)}]");
			Console.Write($"\t[Type: {BitConverter.ToUInt16(data, 16)}]");
			Console.Write($"\t[Code: {BitConverter.ToUInt16(data, 18)}]");
			Console.Write($"\t[Value: {BitConverter.ToInt32(data, 20)}]");
			Console.WriteLine();
		}
	}
		/*
Ref: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
Ref: https://www.kernel.org/doc/Documentation/input/event-codes.txt

[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 57 ABS_MT_TRACKING_ID]    [Value: 268]  Unique ID of initiated contact
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 53 ABS_MT_POSITION_X]    [Value: 337] Center X touch position 
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 54 ABS_MT_POSITION_Y]    [Value: 403] Center Y touch position
[Sec: 1551171811]    [USec: 619071]    [Type: 1 EV_KEY]    [Code: 330 BTN_TOUCH]    [Value: 1] 
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 0 ABS_X]    [Value: 337] 
[Sec: 1551171811]    [USec: 619071]    [Type: 3 EV_ABS]    [Code: 1 ABS_Y]    [Value: 403]
[Sec: 1551171811]    [USec: 619071]    [Type: 4 EV_MSC]    [Code: 5 MSC_TIMESTAMP]    [Value: 0] 
[Sec: 1551171811]    [USec: 619071]    [Type: 0 EV_SYN]    [Code: 0 SYN_REPORT]    [Value: 0] Indicate the batch is completed and we can proceed
		*/

All the documentation about the types, code, etc. is available from source of Linux (https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h).

The structure look like the following (4bit):

(C lang)

struct input_event {
	struct timeval time; // 2 unsigned uint32 or uint64
	unsigned short type; // 4 bit
	unsigned short code; // 4 bit
	unsigned int value;
};
Start PositionTypeName
0UINT32TSec (Timestamp seconds) – E3-00-75-5C + skip 4 bytes in 64bit arch
8UINT32TUsec (Timestamp microseconds) – 3F-72-09 + skip 4 bytes in 64bit arch
16UINT16Type – 4A-01
18UINT16Code – 01-00
20INT32Value – 00-00

The official documentation can be found at https://www.kernel.org/doc/Documentation/input/input.txt under the section 3.2.4 evdev and we can read the following:

The event codes are the same on all architectures and are hardware independent.

https://www.kernel.org/doc/Documentation/input/input.txt

How should we test?

It looks obvious, but in windows you might not be able to test/debug. You need a Linux system (Not subsystem). Since I use a touch panel with Linux, I simply logged the event output while I was experimenting the touch panel. I then replayed that same data while writing UT (TDD). After your TDD passes and that your event gets out as expected, you can then have the desired flow within your application. If you’ve never experimented, know that you can do remote debugging from Visual Studio (or code) to a Linux host. It works well and you might be able to debug more easily.

Existing library to support you

Here we go directly in the raw content, however, there’s a NuGet package that might help you realize what you need. As discussed in the beginning of the article, the HID is the keyword you want to look for, for such development. I haven’t played that much with the following library, but you should definitely use it in case you want to manage the devices from Dotnet. That library is OS agnostics and does not really care if you are on Windows, Linux or MacOS.

The only issue with that library is the lack of documentation. The best is to read the code itself or simply code everything by yourself.

]]>
https://blog.honosoft.com/2019/03/12/linux-dotnet-read-from-a-device-file/feed/ 1
Dotnet and Reflection – Exploration By Example https://blog.honosoft.com/2019/03/06/dotnet-and-reflection-exploration/?utm_source=rss&utm_medium=rss&utm_campaign=dotnet-and-reflection-exploration https://blog.honosoft.com/2019/03/06/dotnet-and-reflection-exploration/#respond Wed, 06 Mar 2019 14:21:00 +0000 https://blog.honosoft.com/?p=458 Continue Reading]]> Today, I will be talking about how to use reflection in order to generate code. The reflection in C# is quite strong and useful for many scenario. In this example I will simply use a dictionary created from a JSON object and then based on the “key” of that dictionary, I will create a special object if it applies, including the value. This could be useful if you receive a lot of data in from a third party, but where you would be interested only in a few keys in order to generate data somewhere in your system. Here we will not go through the performance part, but, simply know that you can alter the algorithm in order to cache the key.

What are we building?

The example will be simple and will be doing the following:

  • Take JSON input
  • Transform that JSON into an object (In this case, an object containing a dictionary)
  • Pass through all the items from the dictionary and match the key against an enum which will be used to generate code
  • Use reflection on the dictionary key enum (if it exists) and create a new special object with the dictionary value
  • Print out the generated data using JSON prettify option

Building block #1 – JSON input

The JSON input will be quite simple, it will be an object containing a simple dictionary.

{ "MyDictionary": { "Key1": 1, "Key2": 2, "Key3": 3.1416 } }

Building block #2 – Basic objects structure

We now know the JSON structure, so let’s create our basic objects. Here we go a bit further than simply creating objects, we also create the Attribute which will be used during the “Reflection”.

class SampleObject
{
    public Dictionary<string, decimal> MyDictionary { get; set; }
}
public class CustomAttribute : Attribute
{
    public string CustomValue { get; set; }

    public CustomAttribute(string customValue)
    {
        CustomValue = customValue;
    }
}
 enum MyDictionarySpecialKeys
 {
     [Custom("Simple Value")]
     Key1,
     [Custom("Another Simple Value")]
     Key3
 }
 class MySpecialItem
 {
     public string Special { get; set; }
     public string OriginalKey { get; set; }
     public string Value { get; set; }
 }

Dive in the reflection

Now that we have all the basic object, let’s talk about the Reflection. I will not repeat what you can find on the MSDN, but rather show you directly a possible usage. The Reflection, as the name indicate, can go and read through the assembly and see how the objects are made and give you useful information. Recently, I used it in order to take a JSON configuration (appsettings section) and then generate code to be executed in the background based on that. I will not explain further that application.

Let’s begin, we will be taking our “Attribute” from the enum value and use the value provided. Then from that, instead of simply returning a simple string, let’s return a new Object. In this implementation, I will be using an extension method for simplicity. The same could be done using a simple method.

public static class CustomAttributeExtension
{
    internal static MySpecialItem GenerateEvent(this MyDictionarySpecialKeys myKey, decimal val)
    {
        // Get the member from the Enum
        var memberInfo = typeof(MyDictionarySpecialKeys)
            .GetMember(myKey.ToString())
            .FirstOrDefault();

        // If the enum member exists, then let's get the attribute
        if (memberInfo != null)
        {
            var attr = (CustomAttribute)memberInfo
                    .GetCustomAttributes(typeof(CustomAttribute), false)
                    .FirstOrDefault();

            if (attr == null)
            {
                // Should skip, if not exists
                return null;
            }

            // If enum contains the attribute let's create an object
            return new MySpecialItem
            {
                Special = attr.CustomValue,
                OriginalKey = myKey.ToString(),
                Value = val.ToString(CultureInfo.InvariantCulture)
            };
        }

        // Should skip
        return null;
    }
}

Let’s put all together in a program

If we take all of what we’ve done so far and put this into a program. We will have a JSON having 3 items in a dictionary. However, that dictionary will have only two entries mapped on the strongly typed enum. From that enum, the two enum members will be decorated with a custom attribute giving a special extra meaning to the value.

That put into code will give you something like this:

public class Program
{
    public static void Main()
    {
        var jsonInputSample = "{ \"MyDictionary\": { \"Key1\": 1, \"Key2\": 2, \"Key3\": 3.1416 } }";
        Console.WriteLine($"Input: {jsonInputSample}");

        var deserializedObject = JsonConvert.DeserializeObject<SampleObject>(jsonInputSample);
        var generatedSpecialItems = new List<MySpecialItem>();

        Console.WriteLine();
        Console.WriteLine("Content of [MyDictionary]:");

        foreach (var dictionaryItem in deserializedObject.MyDictionary)
        {
            Console.WriteLine($"\tDictionary [Key: {dictionaryItem.Key}] [Value: {dictionaryItem.Value}]");
            if (Enum.TryParse(dictionaryItem.Key, true, out MyDictionarySpecialKeys specialKey)
                && Enum.IsDefined(typeof(MyDictionarySpecialKeys), specialKey))
            {
                var specialItem = specialKey.GenerateEvent(dictionaryItem.Value);

                if (specialItem != null)
                {
                    generatedSpecialItems.Add(specialItem);
                }
            }
        }

        Console.WriteLine();
        Console.WriteLine("Result (Could be special items to be inserted in a DB):");
        Console.WriteLine("-----------------------------------------");
        Console.WriteLine(JsonConvert.SerializeObject(generatedSpecialItems, Formatting.Indented));
        Console.WriteLine("-----------------------------------------");
        Console.WriteLine("Completed");
        // Console.ReadKey(); // If you wish to wait when you run from Visual Studio and not command line.
    }
}

And the output result will be:

Input: { "MyDictionary": { "Key1": 1, "Key2": 2, "Key3": 3.1416 } }

Content of [MyDictionary]:
        Dictionary [Key: Key1] [Value: 1]
        Dictionary [Key: Key2] [Value: 2]
        Dictionary [Key: Key3] [Value: 3.1416]

Result (Events to be created):
-----------------------------------------
[
  {
    "Special": "Simple Value",
    "OriginalKey": "Key1",
    "Value": "1"
  },
  {
    "Special": "Another Simple Value",
    "OriginalKey": "Key3",
    "Value": "3.1416"
  }
]
-----------------------------------------
Completed
]]>
https://blog.honosoft.com/2019/03/06/dotnet-and-reflection-exploration/feed/ 0
Create plugins in Dotnet Core or what we call Managed Extensibility Framework https://blog.honosoft.com/2018/11/29/create-plugins-in-dotnet-core-or-what-we-call-managed-extensibility-framework/?utm_source=rss&utm_medium=rss&utm_campaign=create-plugins-in-dotnet-core-or-what-we-call-managed-extensibility-framework https://blog.honosoft.com/2018/11/29/create-plugins-in-dotnet-core-or-what-we-call-managed-extensibility-framework/#respond Thu, 29 Nov 2018 13:27:41 +0000 https://blog.honosoft.com/?p=352 Continue Reading]]> In Java we have the Service Loader that can add plugin(s) within your application at startup. It basically means you can extend your application with a set of new features. This same service loader can also at some extent add those plugins at runtime by detecting new files within the plugin folder.

If we look what we have in Dotnet world, we have something very similar that have been existing for a long time. It’s still rare to see such system being used. It will more likely already exists without having anyone really having to maintain it. Since in Dotnet Core 2.1, the namespaces changed a bit, it makes it interesting to explore with you. I guess that if you are on this post, you are most likely a technical guy. I assume that you’ve already done some research on what it is and that you’d like an example (step by step) to really show how easy it is to build. In this article we’ll cover only partially the Manageed Extensibility Framework (MEF).

Edit: For Dotnet Core 3, there’s a new feature, so follow this link to MSDN.

What is the Managed Extensibility Framework (A.k.a.: MEF)?

Often you will hear about the acronym MEF instead of its long name “Managed Extensibility Framework”.  In the past we were also talking in parallel about the MAF which stands for Managed Add-in Framework (See details here). MAF still exists within the Dotnet Framework with Windows Client (WPF for example), however in Dotnet Core you it is not available “yet”.

In the introduction, I stated that if you’ve used Java, you know the main idea about it. For the others, the general idea is, that when, you or a third party, have finished to create your plugin you drop your JAR in the plugins folder.  In C#/Dotnet world, MEF and MAF are using the same principle, whereas instead of dropping a Jar you drop your “Assemblies”.

Let’s go back to the question, what is MEF? It’s a contracts based extensibility managed by imports and exports within your programs or libraries. You will then make an extensible application by implementing interfaces and then in your main application you will have a provider to provide the implemented contracts (interfaces) implemented. The Assemblies that you want to import in your main application require the exports. Those exports are going to be discovered through “Reflection“.

Let’s take the famous calculator as an example where you could extends the functionalities (plus, minus, division, etc.). Each of those functionalities could be a provided within different “Assemblies” loaded at the startup of your application. There would be no need of project references to those assemblies within your main application. So if you add or remove “Assemblies”, functionalities are going to be added or removed. The magic comes from the exports you would discover. For the calculator example, you could have a “Name” (Square Root) for what it is and the official “Operator” (√) for the discovered feature. You would then have an implementation of the operator receiving “Number 1, …” producing a new result (Contract within an interface shared between all the libraries, a.k.a. interfaces). The Imports are, as stated previously, most likely be in your main executable (WebApi, Console, etc.).

How does it works?

As stated above, the main program will do your discovery of shared contracts API’s. Those Exports/Imports are being assigned using attributes on your classes. Those attributes, to name only a few, will look like [import], [ImportMany], [ExportMetadata(“something”, “here”)], [Export(typeof(IYourInterface))], etc.. As you can see, the import will import only one instance while ImportMany will create an IEnumerable. Those attributes can be found within the Nuget package “System.Composition.AttributedModel“. Keep that package in your mind since it will become handy in a later stage.

If you wonder why the ExportMetadata can become something really cool. I would say that you could have some filtering that could come from there regarding your application. That could also help in order to give some capabilities about your module (Extensibility).

Practice time – Let’s build a simple App (WebApi/SPA)

Of course, we’re not going to be building the famous calculator sample. It’s all over the place already. I don’t say that the calculator is a not a great example, but my point here is not to copy what already exists. In this “exercise”, you will be scratching the surface of the Extensibility. In a real-world scenario, you could have logs that need to strips out content based on rules that are either common or specific. Let’s say, the customer numbers are not the same from customer to customer as such that each customer requires to have different custom rules mixed with general ones. That one could be done using simply a regex, but let’s say it goes to something really complex like the passport numbers. In that case, you could implement custom rules for each customer (per country) and simply drop those rules within the plugin folder. That way, you could even request the customer to build the plugin themselves and if you thrust them enough then simply allow them to update their plugin through a portal (upload, push, and either restart your service or then load at runtime the updated plugin).

The current exercice is to simply create a simple Web Application written in Dotnet Core 2.1 using a basic UI querying the API. It will then show the result in raw of what plugin have discovered.

Scaffold a simple WebApi and Projects

In previous posts, I said that I like to use my own template since it’s quite a good starter. It’s compact and fast enough for anyone with basic knowledge of Dotnet Core and VueJs. The UX is using, instead of the common Bootstrap 4, something lighter called Picnic CSS. You can download/update the template using > dotnet new -i HoNoSoFt.DotNet.Web.Spa.ProjectTemplates(Documentation here). This exercise can also be accomplished within a Console application. However, I think the most common scenario will be around the usage of a WebApplication and then extend it somehow. Let’s create the structure of the project (Command line, it’s easier):

> mkdir Sample.Mef
> cd Sample.Mef
Sample.Mef> dotnet new vuejs-picnic -o Sample.Mef.Web
Sample.Mef> dotnet new classlib -o Sample.Mef.Api
Sample.Mef> dotnet new classlib -o Sample.Mef.GiveMeOne
Sample.Mef> dotnet new classlib -o Sample.Mef.GiveMeFive
Sample.Mef> dotnet new sln
Sample.Mef> dotnet sln add .\Sample.Mef.Web\Sample.Mef.Web.csproj
Sample.Mef> dotnet sln add .\Sample.Mef.Api\Sample.Mef.Api.csproj
Sample.Mef> dotnet sln add .\Sample.Mef.GiveMeOne\Sample.Mef.GiveMeOne.csproj
Sample.Mef> dotnet sln add .\Sample.Mef.GiveMeFive\Sample.Mef.GiveMeFive.csproj
Sample.Mef> cd Sample.Mef.Web
Sample.Mef\Sample.Mef.Web> npm install
Sample.Mef\Sample.Mef.Web> dotnet run

Normally after completing the previous command, you should have the Web Project starting. The URL of the project will be displayed within the console or if you are in Visual Studio, it will open the start page by itself.

For the next part of the blog I will be using Visual Studio Community, but feel free to use VS Code.

Exercise: Structure Application Tree

In the following screen capture, some renaming have already taken place (1) and as indicated in the image, some files needs to be removed (2) or modified (3).

Not marked on the picture:

  • Remove IWeatherProvider.cs 
  • Update ServiceCollectionExtensions.cs (remove the injection of the IWeatherService).

Implement the contract in GiveMeOne & GiveMeFive

Some project will require the reference in order to be able to use IGiveNumber interface. Those relation are shown within one of the previous section (UML like)

  • Add the reference of Sample.Mef.Api project to
    • Sample.Mef.GiveMeOne
    • Sample.Mef.GiveMeFive
    • Sample.Mef.Web

The interface “IGiveNumber.cs” look like:

namespace Sample.Mef.Api
{
    public interface IGiveNumber
    {
        int GiveInt();
    }
}

This contract is going to be implemented within “GiveOne.cs” and “GiveFive.cs”

using Sample.Mef.Api;

namespace Sample.Mef.GiveMeOne
{
    public class GiveOne : IGiveNumber
    {
        public int GiveInt()
        {
            return 1;
        }
    }
}
using Sample.Mef.Api;

namespace Sample.Mef.GiveMeFive
{
    public class GiveFive : IGiveNumber
    {
        public int GiveInt()
        {
            return 5;
        }
    }
}

Now you should be able to build with no errors.

Add the Composition/Extensibility to Import/Export

You remember the nuget package from earlier? It gives the simple Import/Export and a few other basic functionalities. In our case, we will be using it simply for exporting the implemented interface. Let’s install it:

  • Add Nuget package “System.Composition.AttributedModel” to
    • Sample.Mef.GiveMeOne
    • Sample.Mef.GiveMeFive

Add the “Export” attribute

Now, let’s update our two previously defined classes (GiveOne, GiveFive) and add our exports. Those exports allow the Reflection, in a later stage, to discover the implementations. Some customization could also be done, but for that, rely on the MSDN documentation.

Within the two previous project mentioned, add the following Export attribute:

using System.Composition;
// Some code...

[Export(typeof(IGiveNumber))]
public class ...// some code...

Add the “Import” matching the previous “Export” within a provider

This part is only modify “Sample.Mef.Web” project. As we’re going to create a new provider, let’s make it properly by using Dependency Injection (DI). Within the provider, we will also be using System.Composition. This is will help doing the discovery of the plugin(s).

  1. Add the Nuget package reference to the project: “System.Composition“
  2. Create an IGiveNumberProvider + GiveNumberProvider
  3. Update the startup to inject the new provider

We will put IGiveNumberProvider and GiveNumberProvider within the same file, but it’s a sample and you can also do as you please.

using Sample.Mef.Api;
using System.Collections.Generic;
using System.Composition;
using System.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

namespace Sample.Mef.Web.Providers
{
    public class GiveNumberProvider : IGiveNumberProvider
    {
        public GiveNumberProvider()
        {
            Compose();
        }

        [ImportMany]
        public IEnumerable<IGiveNumber> Services { get; private set; }

        public IEnumerable<(string Id, int Value)> GetNumberFromAllFoundServices()
        {
            return Services.Select(f => new { Id = f.GetType().ToString(), Value = f.GiveInt()})
                .AsEnumerable()
                .Select(c => (c.Id, c.Value))
                .ToList();
        }

        private void Compose()
        {
            // Catalogs does not exists in Dotnet Core, so you need to manage your own.
            var assemblies = new List<Assembly>() { typeof(Program).GetTypeInfo().Assembly };
            var pluginAssemblies = Directory.GetFiles("d:\\plugins\\", "*.dll", SearchOption.TopDirectoryOnly)
                .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath)
                // Ensure that the assembly contains an implementation for the given type.
                .Where(s => s.GetTypes().Where(p=> typeof(IGiveNumber).IsAssignableFrom(p)).Any());

            assemblies.AddRange(pluginAssemblies);

            var configuration = new ContainerConfiguration()
                .WithAssemblies(assemblies);

            using (var container = configuration.CreateContainer())
            {
                Services = container.GetExports<IGiveNumber>();
            }
        }
    }

    public interface IGiveNumberProvider
    {
        IEnumerable<(string Id, int Value)> GetNumberFromAllFoundServices();
    }
}

The code should be clear enough in order for you to understand what it does. However, let’s explain quickly:

  • It’s important to note that I’ve set the Assemblies for the extensibility available in the “d:\plugins\” (Please adapt)
  • We filter using reflection on the assemblies. This will enforce keeping only those that have at least one implementation from IGiveNumber
  • In case the assembly can be loaded from the container, we take the related “Export” implementing IGiveNumber and push that within the services
  • You can now access the implementations through the “Services” (Property field)
    • I use the Tuple as a return type, some of it is new in the C# 7+. In case you’ve missed that new feature, go on the following Blogs MSDN article

Add the Dependency Injection (DI)

Now that the provider is ready, let’s add the DI within Startup.cs. The usage of Singleton is privileged since we don’t want to rediscover at each HTTP requests.

// Code ...
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // code ...

            // Example with dependency injection for a data provider.
            services.AddSingleton<IGiveNumberProvider, GiveNumberProvider>();
        }
// Code ...

Alter the existing “api/values” endpoint in order to get the provider “values”.

using Microsoft.AspNetCore.Mvc;
using Sample.Mef.Web.Providers;

namespace Sample.Mef.Web.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly IGiveNumberProvider _giveNumberProvider;

        public ValuesController(IGiveNumberProvider giveNumberProvider)
        {
            _giveNumberProvider = giveNumberProvider;
        }

        // GET api/values
        [HttpGet]
        public IActionResult Get()
        {
            return Ok(_giveNumberProvider.GetNumberFromAllFoundServices().ToList().Select(f=> new { f.Id, f.Value }));
        }

        // Code
}

It’s now mostly complete. I would even say that you must put your project Assemblies (DLL) in “d:\plugins” or where you’ve decided to discover them. The assemblies we’re going to look for are described next.

Time to test by moving your dll’s in the plugins folder

Since you’ve already built, or maybe not yet, your solution, it’s now the time to bring the assemblies (dll) from Sample.Mef.GiveMeOne, Sample.Mef.GiveMeFive available through the bin directory. You then move them to the “d:\plugins”, or the reconfigured folder from previous step. When you copy your bin folder, you need to copy not only the Sample.Mef.GiveMeOne, you also need to copy it’s dependencies (i.e.: Sample.Mef.Api). Since the System.Composition is included in the Web project, it is not mandatory to have a copy in related assemblies plugin folder.

The plugin folder will then contains:

  • Sample.Mef.GiveMeOne.dll
  • Sample.Mef.GiveMeFive.dll
  • Sample.Mef.Api.dll

Start your application and look at the results

Purely speaking, do a dotnet run from the Web application folder, or in Visual Studio push F5.

Go see if your API’s is answering properly before the last step

[Optional] Update your UX in order to do the call

The output is going to simply show a list from the homepage. The extensions to your project, after being loaded, will be displayed within the result of the API call.

<template>
  <div>
    <page-title title="Give the Extensions" />
    <p>He you will be finding your loaded extensions:</p>
    <ul>
      <li v-for="(data, idx) in numbers" :key="idx">{{data.id}}
          <ul>
            <li>{{data.value}}</li>
          </ul>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
      numbers: []
    }
  },

  mounted() {
    this._getNumbers()
  },

  methods: {
    async _getNumbers () {
      let response = await this.$http.get(`api/values`)
      this.numbers = response.data
    }
  }
}
</script>

Simply run the application and go on the home page. In my case, while using IIS Express, it gave me https://localhost:44307

Final result

Extra Resources

Those resource were not used that much during this blog post, but I think it can help people to also understand how it works in a different context.

  • Official: https://docs.microsoft.com/en-us/dotnet/framework/mef/
  • https://dotnetthoughts.net/using-mef-in-dotnet-core/
  • https://blog.softwarepotential.com/porting-to-net-standard-2-0-part-2-porting-mef-1-0-to-mef-2-0-on-net-core/

Thank you!

By the way, all the source code is available in GitHub (personal @Nordes repository). Enjoy 😉

Featured image original is from: https://unsplash.com/photos/l090uFWoPaI
]]>
https://blog.honosoft.com/2018/11/29/create-plugins-in-dotnet-core-or-what-we-call-managed-extensibility-framework/feed/ 0
ChartJs with SonarQube API’s – Make your own reports https://blog.honosoft.com/2018/11/22/chartjs-with-sonarqube-apis-make-your-own-reports/?utm_source=rss&utm_medium=rss&utm_campaign=chartjs-with-sonarqube-apis-make-your-own-reports https://blog.honosoft.com/2018/11/22/chartjs-with-sonarqube-apis-make-your-own-reports/#respond Thu, 22 Nov 2018 14:30:28 +0000 https://blog.honosoft.com/?p=390 Continue Reading]]> Recently asked to do reports every two weeks to all the teams about their good/bad behaviors and at the same time look if the guidelines were somewhat respected, I decided to build a dashboard using ChartJs and a connection to SonarQube via a local proxy built in Dotnet Core. That report is not built in order to hit the team or anything, it’s more in order to know our current situation and what we should improve. We can also relate the data from Jira (using the api’s) in order to know if we’re improving our bug ratio.

This article will only do a quick start using the template I’ve created recently in Dotnet Core. I won’t put too much details around the code since it’s quite straight forward.

Pre-requisites

  • Dotnet core 2.1 (at the time of writing)
  • NodeJs + NPM (>10 at the time of writing)
  • Visual Studio Code OR Visual Studio Community/Professional
  • A browser (Chrome, or anything else)

Install a quick start from a Dotnet Core template

As mentioned before, you will be using a quick start I created. I use it quite often, so there’s improvement in it happening regularly. So I suggest you to look for updates once in a while. It gives a front-end that could run without backend, and a backend in dotnet core 2.1 ready to serve API’s (including swagger).

> dotnet new -i HoNoSoFt.DotNet.Web.Spa.ProjectTemplates

Once installed, let’s create our template (vuejs-picnic-table):

> mkdir Sonar.Reports
> cd Sonar.Reports
> dotnet new vuejs-picnic-table

And to be sure it works, then let’s run at least in development mode.

> npm install
> dotnet run --environment="Development"

The installation of the NPM packages are needed most likely the first time due to the nature of the project. This could be automated, but would make the project takes a lot of time before even being able to start editing any files. (Personal choice here)

The result should look like the following screen (https://localhost:5001/) OR the demo page available here.

You might get an error the time Webpack generate the files. In that case, simply hit refresh (F5)

Once the application is running, keep it running while you edit your files (next step). You will simply have to hit F5 to refresh the changes.

Clean or add route to the template

Delete all the un-necessary things or simply add a new page. We’ll go here with the latter.

  • Create the file: ./ClientApp/pages/sonar.vue
    • <template>
       <div>
         <page-title title="Sonar" />
       </div>
      </template>
      
      <script>
      export default {
        
      }
      </script>
      
  • Add the new route for the page in ./ClientApp/router/routes.js
    • Add a import in the import section: import Sonar from 'pages/sonar'
    • Add the route after about:  { name: 'Sonar', path: '/:lang?/sonar', component: Sonar, display: 'Sonar', i18n: 'route.sonar', icon: 'icon-home', meta: { order: 6 } },
  • Add the translation/I18n for the new menu in ./ClientApp/_i18n/lang/en.js
    • In the routes group under about, add: sonar: 'Sonar'
  • Save all this and hit F5 in the browser

Congratulation! You’ve created your first page in VueJs. Now it is the time to fill the data and before that, we need to create a service provider.

Access to Sonar Qube API’s from C#

Create your provider in C#, because if you’re not on the same server as your SonarQube, you might get some issue by only using the front-end. Here, we will be creating a simple proxy without any parameters. For those of you who didn’t know, the API is available at your SonarQube URL like the following: https://mySonarQube.url.com/api/.

Generate an API key for your user

  • Go in your account (top right icon)
  • Go under security
  • Generate a new token (The name does not matters, but let’s name it: Sonar.Reports)
  • Copy and paste the key somewhere you won’t loose. Otherwise you will have to re-generate the API token.

Open your project in VS Code or Visual studio

If you’re lucky enough, you will have a Properties folder, otherwise you will have to create it and add the file launchSettings.json

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false, 
    "anonymousAuthentication": true, 
    "iisExpress": {
      "applicationUrl": "http://localhost:21535",
      "sslPort": 44307
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "Sonar.Reports": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "api/values",
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Update your ./Startup.cs and add a new Service configuration:

public void ConfigureServices(IServiceCollection services){
  // ...
  services.AddHttpClient();
  // ...
}

Create the file ./Controllers/SonarController.cs and add the following code:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace Sonar.Reports.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class SonarController : ControllerBase
    {
        private HttpClient _sonarHttpClient;

        public SonarController(IHttpClientFactory httpClientFactory){
          _sonarHttpClient = httpClientFactory.CreateClient("sonar");
          _sonarHttpClient.BaseAddress = new Uri("https://sonar.yourSonarQube.com/api/");
          var basicToken = Convert.ToBase64String(Encoding.UTF8.GetBytes("f6d6fecfdcd7836612a0fef4a3f307b7725f8387:"));
          _sonarHttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", basicToken);
        }

        /// <summary>
        /// GET Sonar data
        /// </summary>
        /// <returns>Returns an enumerable of value</returns>
        [ProducesResponseType((int)System.Net.HttpStatusCode.OK)]
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            var result = await _sonarHttpClient.GetAsync("measures/search_history?component=yourComponentName&metrics=bugs%2Ccode_smells%2Cvulnerabilities%2Creliability_rating%2Csecurity_rating%2Csqale_rating&ps=1000");
            
            // Not perfect but it will do for the sample.
            return Content(await result.Content.ReadAsStringAsync());
        }
    }
}

Where:

  • www.yourSonarQube.com is your address of your Sonar Qube
  • The token you’ve created earlier replaces f6d6fecfdcd7836612a0fef4a3f307b7725f8387 (Don’t forget to keep the “:”, it is mandatory in a basic authentication, where it’s usually user:password in Base 64)
  • yourComponentName is replaced by your real component name in Sonar Qube (To get it, open sonar qube, go in your project you want to test, and then take the value from the URL. It’s always there.)

Now, restart your application (Ctrl+C and then re-type dotnet run, if you’re in console mode).

You can test the endpoint by going to http://localhost:5001/swagger/ and use the swagger UI in order to test it. If you have errors, you will see it in your console (or output in Visual Studio).

Add the ChartJS to your project

Go in your root folder where your project is located (csproj) and then type the following command in order to install Chart.Js:

> npm install chart.js --save

Now that you have installed this new dependency, you can add the Chart.Js within your VueJs application.

Edit your ./ClientApp/pages/sonar.vue and add a default Chart.Js available through all their demo on their site. Here, I will be using the line API

<template>
  <div>
    <page-title title="Sonar" />

    <canvas ref="chart" id="canvas"></canvas>
  </div>
</template>

<script>
// Import ChartJS
import Chart from 'chart.js'

// Colors
var chartColors = [
  'rgb(255, 99, 132)',
  'rgb(255, 159, 64)',
  'rgb(255, 205, 86)',
  'rgb(75, 192, 192)',
  'rgb(54, 162, 235)',
  'rgb(153, 102, 255)',
  'rgb(201, 203, 207)'
]

export default {
  data () {
    return {
      chart: null,
      chartConfig: {
        type: 'line',
        data: {
          labels: [],
          datasets: []
        },
        options: {
          responsive: true,
          title: {
            display: true,
            text: 'Enjoy your Chart.Js with Sonar'
          },
          tooltips: {
            mode: 'index',
            intersect: false,
          },
          hover: {
            mode: 'nearest',
            intersect: true
          },
          scales: {
            xAxes: [{
              display: true,
              type: 'time',
              scaleLabel: {
                display: true,
                labelString: 'Date'
              }
            }],
            yAxes: [{
              display: true,
              scaleLabel: {
                display: true,
                labelString: 'Value'
              }
            }]
          }
        }
      }
    }
  },

  mounted () {
    this.chart = new Chart(this.$refs.chart, this.chartConfig)
    this._loadSonarData()
  },

  methods: {
    _loadSonarData: async function () {
      let response = await this.$http.get(`./api/sonar`)
      var sonarData = response.data

      // We now have measures + data
      var measures = sonarData.measures
      let allLabels = []
      for (var m = 0; m < measures.length; m++) {
        let currentColor = chartColors[m % chartColors.length]
        var data = {
          label: measures[m].metric,
          backgroundColor: currentColor,
          borderColor: currentColor,
          tension: 0, // If you don't want bezier curves
          data: [],
          fill: false,
        }

        for (var h = 0; h < measures[m].history.length; h++) {
          let date = new Date(measures[m].history[h].date)
          if (allLabels.indexOf(date) < 0) {
            allLabels.push(date)
          }

          data.data.push(measures[m].history[h].value)
        }
        
        this.chartConfig.data.datasets.push(data)
      }

      this.chartConfig.data.labels = allLabels
      this.chart.update()
    }
  }
}
</script>

Results

If you’ve followed and didn’t make any big mistakes, the result should looks somewhat like the following

Next step?

Basically, yes, there’s a next step. Since you have been able to call the Sonar Qube Api’s, you now know that you can also configure all the requests to it. For example, you could say “I want data for the last 30 days” or in my case, for the last 3 sprints and then draw vertical lines (using a plugin) in order to show exactly where are the sprint and how it went (For example: rush at the end or not).

You could also add a dropdown at the top, or different menu or customization for other projects/stats you’d like to have.

Since you’re using the canvas, don’t forget you could also make a download or extract image using JavaScript. In a way, you could auto-generate a Powerpoint or word document, or other since you have all the data required to do so.

Conclusion

I hope you’ve enjoyed this post.

]]>
https://blog.honosoft.com/2018/11/22/chartjs-with-sonarqube-apis-make-your-own-reports/feed/ 0
NDepend, SonarQube ou pourquoi pas les intégrer ensemble 😉 https://blog.honosoft.com/2018/11/05/ndepend-sonarqube-ou-pourquoi-pas-les-integrer-ensemble-%f0%9f%98%89/?utm_source=rss&utm_medium=rss&utm_campaign=ndepend-sonarqube-ou-pourquoi-pas-les-integrer-ensemble-%25f0%259f%2598%2589 https://blog.honosoft.com/2018/11/05/ndepend-sonarqube-ou-pourquoi-pas-les-integrer-ensemble-%f0%9f%98%89/#comments Mon, 05 Nov 2018 15:29:14 +0000 https://blog.honosoft.com/?p=298 Continue Reading]]> Préface

J’utilises depuis plusieurs années SonarQube (Ou sinon sur le ☁ nuage que sont les SaaS de ce monde, SonarCloud☁) afin d’avoir une idée générale de la qualité ou de la mise en application des lignes directives des développeurs (guidelines). Comme vous vous en doutez, d’autres produits peuvent soit le remplacer ou même y ajouter une “plus value” (R# par exemple). Dans cet article, nous allons traiter de NDepend et de son intégration avec d’autres produits.

Notez que j’ai eu à utiliser ce produit par le passé et j’avais vraiment eu une bonne expérience. Mais qu’en est-il aujourd’hui, plusieurs années on passés et de l’eau à coulé sous les ponts. Depuis quelques années, il est rare que j’aie un besoin essentiel de pousser ce type de produit dans l’entreprise dans laquelle je travaille ou lorsque je faisais de la consultation.

Si votre entreprise est en manque de qualité, où que vous manquez de visibilité sur ce qui se passe du côté des développeurs, ce produit s’adresse vraiment à vous. Cependant, il y a quelques limitations que nous verrons plus loin.

D’ailleurs, ici j’essaierai de rester impartial tout en donnant mon opinion personnelle. C’est article est en partie possible grâce à NDepend, qui m’offre les licences afin de tester la nouvelle version du produit.

Qu’est-ce que NDepend?

Ce que vous permet de faire NDepend est indiqué sur la page https://www.ndepend.com/features/. Voici tout de même un court résumé:

  • Permet de connaître l’état de santé de votre application
  • Permet d’avoir une idée de la qualité de votre code entre les builds
  • L’estimation de dette technique en temps réel (même lorsque vous codez sans faire vos commit/push)
  • Visibilité de la complexité de votre programme à l’aide de graphique interactif et permet ainsi d’éviter de la confusion entre développeur et architecte.
  • Effectuer des requêtes sur votre code, un peu à la manière SQL (CQLinq)
  • Meilleur visibilité des dépendances

Quelles sont les technologies accessibles à NDepend?

Malgré que vous n’avez pas posé la question, il est important de le savoir de facto avant de continuer à lire l’article. C’est d’ailleurs une question légitime que tout décideurs d’entreprise doivent se poser.

De nos jour, il est de plus en plus rare de rencontrer des équipes de développement qui travaillent avec une seule technologie. Où je travaille en ce moment, nous avons listé aujourd’hui les technologies utilisées par une équipe SCRUM afin de créer une grille de compétences. Un autre architecte et moi même avons listés environs 30 connaissances de bases nécessaires si nous voulons des développeurs versatiles sur toutes les parties de l’application couverte par l’équipe. Dans ce cas précis, ce n’est pas vraiment une application, mais environ 8 applications à maintenir et à développer et pas tous en même temps.

Si on va vers quelque chose de plus concret, dans mon cas, je suis spécialiste des technologies Microsoft. Cependant, je travaille avec énormément d’autres langages, et ce, quotidiennement. Ces autres connaissances sont soient pour faire de la validation de code, faire des preuves de concepts (PoC), du développement, du soutient ou encore effectuer des analyses pour les équipes de développement. (Oui je fais un peu de tout, je suis tombé là dedans quand j’étais petit 😉 ) D’ailleurs, en ce moment, je dois travailler sur l’acquisition d’une certification TOGAF en tant qu’architecte d’entreprise. Si on nomme les langages dont je dois valider ou du moins comprendre quotidiennement sont par exemple: C#, Java, Python, JavaScript (NodeJs), TypeScript (Node), etc. Malgré le fait que je ne suis pas expert dans tous ces langages, je n’ai aucune difficulté à lire ou à comprendre le code des autres. Il y a quelques années j’avais vu un plugin fait en Ruby et je trouvais qu’il n’en faisait pas assez, du coup j’avais fait un refactor complet et les gens de la communauté avait apprécié le changement, malgré que la stabilité n’était pas à 100% (ça y était presque, seulement des problèmes de concurrence lors de grandes charges sur les serveurs).

Tout ceci est pour en venir au fait suivant: Les erreurs rencontrées dans un langage sont souvent les mêmes que dans les autres.

Pour en revenir au titre de la section, répondons à la question plus simplement:

  • C# (Quasi toutes les versions, dont, dotnet core 1.0, 1.1, 2.1 et 2.2 preview!)
  • VbNet
Notez que ça peut s’intégrer au produit de JetBrains – TeamCity dont je n’ai jamais eu la chance de tester.

Personnellement, j’adore travailler avec C# (TCP, UDP, etc.) et encore plus depuis l’arrivée de Dotnet Core 2+. Lorsque vous avez à travailler sous TCP/UDP (incluant le web), vous devez avoir les bons outils afin d’éviter d’introduire quoi que ce soit en dettes technique. Plus on attend pour les fixer, plus on le regrette du fait que ça devient quasi impossible à corriger. Du coup, un produit comme NDepend peut venir à la rescousse. Sa version développeur s’intègre comme étant un plugin dans Visual Studio et sa license serveur peut s’intégrer sur votre serveur de build (voir plus bas).

NDepend – Les builds automatisés

Lorsque mes builds automatisés sont sur des projets public, j’utilises presque toujours AppVeyor en mode gratuit avec une connexion à mes repos GitHub ou BitBucket. Cet outil vous offre une intégration de premier plan en autant que vous sachiez ce que vous faites. Les images utilisées sont compatible Windows, donc vous pouvez compiler vos projets sans problème. En ce moment, où je travaille, j’utilise soit Bamboo ou GitLab (intégration simple et continue). Par le passé, en mode privé, j’utilisais Jenkins, TFS, ainsi que d’autres outils. Dernièrement, j’ai installé un serveur privé GitLab afin de faire des projets R&D dans mon département. Ça permet de partager simplement notre code en interne et permet aux autres de pouvoir faire comme sur github et participer dans vos projets. Je dirais qu’aujourd’hui, ça correspond à la plupart des besoins professionnels pour petites ou grandes entreprises. Je préfère l’intégration avec AD ainsi que le tout inclus dans une seule application (Wiki, Build, Source, …). C’est d’ailleurs très simple d’utilisation avec les images dockers lors de builds en parallèles. Tout ça pour dire qu’à un certain point, tout ces outils ne sont qu’une question de goût. En ce qui concerne le Cloud, vous pouvez utiliser AppVeyor ou d’autres services Azure qui s’apparente au service TFS, GitLab online, Travis, etc..

En ce qui concerne la version NDepend, il peut s’utiliser officiellement avec TeamCity (JetBrains), Jenkins (ou anciennement Hudson), AppVeyor, Team Foundation Server (TFS), Azure DevOps (ex VSTS) ainsi que quelques autres. Aucune mention sur Bamboo est disponible sur le site, mais étant donné que l’agent Bamboo peut s’exécuter sous Windows, je ne vois aucuns soucis à l’horizon. Important: N’oubliez pas que vous devrez avoir une licence serveur pour exécuter des Builds sur cette machine (Actuellement la license est détaillée au prix de 799€).

Étant donné qu’AppVeyor est déjà traité dans un autre article de ce site, j’irai vers cette solution. Sinon, Jenkins ou encore Bamboo seront de tout aussi excellents choix. Pour tester le produit en local, je vous dirais de tout simplement prendre une image Docker pour SonarQube et de même que pour le serveur principal Jenkins. Ensuite, vous pouvez configurer votre agent Windows sur votre machine même et de le configurer sous Jenkins. Concernant Bamboo, je n’ai jamais eu la chance de créer un agent, mais si c’est aussi simple que Jenkins, ça ne doit pas être très loin d’un clic. L’idée derrière la virtualisation sous docker, c’est que vous pouvez éviter de polluer trop votre environnement afin de tout tester.

De nos jours, je dirais qu’il est de plus en plus courant de rencontrer des environnements professionnels avec la stack Atlassian (Confluence, Bamboo, Jira, BitBucket/Stash). Tel qu’indiqué plus tôt, le site NDepend ne traite aucunement de plugin pour Bamboo. Je vous dirais dans ce cas de configurer comme si c’était pour votre PC, sauf que vous ajoutez une capacité à votre agent de build qui pointe sur le fichier EXE de NDepend. D’ailleurs, ceci pourrait être sans doute le sujet d’un autre billet/blog (soit ici ou bien pourquoi pas sur le site de NDepend).

Dans la prochaine section, nous allons traiter l’intégration sous AppVeyor. Note à part, si ce n’est pas déjà dit plus tôt, il est impératif d’avoir un agent Windows afin d’exécuter l’analyse et ainsi générer un “Site web” ou le build pour SonarQube. Le site web peut ensuite être mis à la disposition de votre équipe par le billet d’une automatisation (artifact + déploiement sur votre serveur IIS, Apache ou autre car c’est un site en HTML pure).

Prise en main des outils [a-Z] ou du moins le minimum

Achat, ou essaie

Avant d’acheter ce type de produit, il est toujours conseillé de tester et dans le cas de soucis ou incompréhension, communiquer directement avec le support. Ceci vous permettra de mieux comprendre le produit et ses possibilités dans votre entreprise. Afin de tester, vous n’aurez qu’à remplir un formulaire (14 jours d’essaie gratuit). Dans la plupart des cas, pour simplement tester, n’utilisez pas une licence officiel serveur pour faire que 2 Builds ;).

Téléchargement

Une fois la licence acheté ou le formulaire de 14 jours d’essaie rempli, vous devrez recevoir par e-mail un lien pour télécharger le produit (https://www.ndepend.com/download).

NDepend

Contenu du téléchargement

Vous venez de télécharger NDepend (~16 MO) et vous vous rendrez compte que l’archive est plutôt simple. Car, oui, ce sera une archive et non un exécutable “self-executable”/”extractable” qui feras l’installation dans un répertoire pour vous. Vous aurez besoin de Unzip l’archive avec soit 7Zip ou l’outil qui vient avec Windows.

Voici le contenu de l’archive:

NDepend – Contenu du Zip

Si vous avez besoin de plus d’information, c’est disponible sur le site officiel ainsi que partiellement dans le fichier README.txt. Si vous n’avez pas trop de temps, voici ici ceux qui vous intéresseront le plus. Dans le cas où la lecture ne vous intéresse pas vraiment, allez visionner la vidéo.

  • NDepend.VisualStudioExtension.Installer.exe
    • L’extension Visual Studio (pour les développeurs 🎉)
  • NDepend.Console.exe (Plus de détails ici en anglais)
    • Ce qui vous servira lors des builds.
  • VisualNDepend.exe
    • Permet de visualiser le tout sans s’intégrer dans Visual Studio. Dans ce cas, vous devrez avoir la clé de la licence sous la main, car ce sera demandé dès l’ouverture du produit.
    • Exemple une fois ouvert
  • Integration/…
    • Contiens les extensions pour SonarQube, TFS, TeamCity, XLT -> * (transformation) ainsi qu’un peu de documentation.

Les autres fichiers, oui ils vous intéresseront et ce sera du cas par cas. Ensuite, pour explorer tout le produit, je vous conseille d’ailleurs de prendre la version démo 14 jours. Si vous aimez, vous achetez, sinon, vous arrêtez.

Le projet NDepend.PowerTool.SourceCode (détails ici en anglais) vous montrera comment interagir avec l’API NDepend directement à partir de votre code. Je recommande d’y jeter un coup d’œil, mais plus afin de voir comment ça peut s’intégrer avec vos produits existant. Vous pourrez ensuite prendre ce qui vous intéresse de l’API et l’appliquer.

Votre quotidien vous appartient, n’oubliez pas!

Rapport de Build avec un de mes projets GitHub (VisualNDepend)

Je vais prendre ici un projet sur lequel j’ai créé et touché dernièrement (badgeIt), mais que je doute que beaucoup de gens l’utiliseront. Le projet a été créé dans le cas spécifique où tout est bloqué par soit des proxy ou encore des comptes d’identification à Sonar et où on ne peut pas récupérer les badges de Sonar sans que les utilisateurs soient connectés. Pour plus de détails sur ce projet, veuillez suivre ce lien vers mon autre article (SonarCloud + AppVeyor + etc.).

Donc ici, nous allons utiliser NDepend SANS l’intégration de l’extension Visual Studio 2017. Nous utiliserons que l’outil en ligne de commande (Console windows) ainsi que l’application VisualNDepend afin de nous “kick starter” ça.

  1. Clone du projet Github: > git clone https://github.com/Nordes/HoNoSoFt.BadgeIt.SonarQube.git
  2. S’assurer que tout build/s’exécute normalement: > dotnet run
    • Note: Le projet est en .NetCore 2.1 et NDepend peut analyser jusqu’à 2.2 (preview en ce moment).
  3. Alternativement au point #2, vous pouvez ouvrir la solution sous Visual Studio 2017 et exécuter avec F5.
  4. Ça affichera une jolie page de test, c’est top. Maintenant, c’est le temps d’arrêter l’application et de préparer le tout pour exécuter NDepend.

Prise en main de VisualNDepend

Commençons tout d’abord par l’application VisualNDepend où un aperçu de l’écran de démarrage est visible dans la section précédente. Cet outil s’adresse à un public plutôt expert ou aux architectes de ce monde qui veulent faire des statistiques et un peu plus. C’est parfait pour faire un rapport qui serait visible en live lors d’une présentation à une équipe agile. C’est aussi très utile afin de repérer les points critique d’une application et ainsi savoir où l’on devrait améliorer soit les dépendances ou bien la quantité de tests. Vous pouvez d’ailleurs, dans un premier temps, créer une “baseline” qui vous servira par la suite de référence dans le temps.

Au cas où vous auriez à suivre l’évolution d’un projet et montrer à vos équipes si tout va dans la bonne direction, c’est parfait. Dans mon cas, je l’utilise à la fin de chaque sprint lors de la rétrospective afin de montrer ce que l’on à fait de bien (ou de moins bien). Ce n’est en aucun cas afin de pointer quelqu’un du doigt. Ça permet d’aussi expliquer aux gens où l’ont doit s’assurer d’une certaine qualité et comment la maintenir.

Voir la quantité de code couvert par vos tests (comme à la SonarQube)

Oui, c’est possible. Cependant vos tests devront générer un fichier de résultat. (Je n’ai pas été en profondeur afin de vérifier avec XUnit ce qu’était mon résultat.) Sachez que c’est possible sans trop d’efforts. Il suffit de suivre les guides; soit sur le site de XUnit ou bien sur le site/blog de NDepend.

Si vous avez ces fichiers de résultats, vous pouvez les intégrer dans l’outil VisualNDepend.

Prise en main de NDepend.Console

Sans doute l’outil qui vous sera le plus utile pour des builds automatisés. Vous avez deux choix afin de vous guider. Soit utiliser l’outil de console afin de vous donner de l’aide, ou bien ouvrir votre navigateur web et aller tout simplement sur https://www.ndepend.com/docs/ndepend-console. Lors de l’exécution de l’application, vous noterez que si vous l’utiliser dans vos builds et que s’il y a une “Quality gate” qui ne passe pas, votre build sera marqué en route.

Afin de pouvoir l’utiliser, vous devrez impérativement vous créer un projet NDepend (votre_projet.ndproj ou votre_projet.xml) afin de gérer toutes les options et configurer sans avoir à lire tout le fichier. Je vous conseille d’utiliser tout simplement VisualNDepend. L’écran de la configuration ressemblera alors à:

Sauvegardez votre projet et ses options, ça vous sera utile pour la suite.

  1. Ouvrir votre console (CMD prompt)
    • WIN + R et taper cmd + la touche entrer
  2. Allez dans votre répertoire ou vous avez votre projet “mon_projet.ndproj”, mais ce n’est pas nécessaire, vous comprendrez à la prochaine étape
  3. Entrer la commande suivante ou bien évidemment vous mettrez les répertoires que vous allez avoir utilisés
    • > D:\Ndepend\NDepend_2018.2.1.9119\NDepend.Console.exe d:\Ndepend\Project\CognitiveServices.ndproj
    • Vous noterez les parties en caractère gras qui sont obligatoire. Oui, vous devrez absolument indiquer le chemin complet de votre fichier ndproj (c’est écrit dans la doc, sinon ça ne fonctionnera pas). Par défaut le rapport sera en HTML, veuillez consulter l’aide afin d’obtenir un rapport sous différents formats.
  4. Pour un projet .Net Core 2.1 ça vous donnera quelque chose qui ressemblera plus ou moins à ce qui suit:
    • Sortie d’écran console

  5. Et voilà c’est terminé.

La sortie produira un site web, si l’on veut, qui vous indiquera les informations, ou plutôt, vous permettra de partager les résultats avec vos équipes de travail.

Site HTML NDepend

Je n’ai pas obtenu la note de A, mais ce n’est pas bien grave.

Visualisation du rapport autre format que HTML

Oui, c’est une possibilité, mais malheureusement je ne traiterai pas du sujet ici. Vous pouvez utiliser la technologie XSLT (XSL Transform). Ça fait un bon moment que je n’ai pas eu à utiliser cette techno, mais ça vous permet de générer un rapport comme bon vous semblera. Vous pourriez, par exemple, créer un rapport qu’avec ce qui vous plait sous un format CSV par exemple.

Intégration dans un projet sur GitHub

Pour intégrer dans un projet GitHub, vous aurez besoin d’y ajouter un fichier .ndproj, car ça vous permet d’avoir les règles prédéfinies. Ensuite, l’ajout du scanner “NDepend.Console” peut soit être ajouté dans votre repository. Mais, vous pourriez tout aussi bien mettre le fichier EXE disponible au téléchargement dans l’un de vos dossiers google drive (archive zip par exemple) ou vous auriez un lien pour télécharger uniquement. Puisque que vous n’allez pas exécuter votre build “si souvent”, surtout sur github, je crois que c’est une solution viable.

Dans mon cas, afin de tester, je ne mettrai pas le fichier dans mon répertoire Github. Je vais plutôt le télécharger au moment du build. Le fichier ne fait que 60kb.

Pour ce qui concerne le fichier de licence, ce sera couvert dans la prochaine section.

Intégration dans AppVeyor (Build)

Et voilà nous y sommes. C’est le temps de faire un merveilleux build automatisé. AppVeyor, si vous ne connaissez pas, vous permet de faire des builds pour les applications .Net ou .Net Core ;). Étant donné, que dans la section précédente vous ne vouliez pas mettre votre licence disponible au grand public. Qu’est-ce que vous avez comme autre option?

  1. Faire comme pour le fichier de console et avoir un lien spécial sur Google Doc, OneDrive, Azure storage, etc.
  2. Le fichier console… est-ce assez? je ne crois pas 🙁 … d’après l’article https://www.ndepend.com/docs/appveyor-integration-ndepend on a besoin de plus de truc)

Intégration de SonarQube et AppVeyor (Build/Publication)

C’est quelque chose de tout à fait possible. Ensuite, tout dépend si votre SonarQube est accessible par le web ou seulement en intranet. Dans le cas où vous auriez votre SonarQube sur Azure ou autre, vous pourrez bien évidemment tout configurer afin de laisser AppVeyor envoyer les données vers votre serveur. N’oubliez pas de créer une clée API afin de pouvoir soumettre les rapports.

Petit rappel: La version gratuite de SonarQube ne permet pas de gérer les branches. Ce n’est pas un problème dans notre cas ici, mais dans le cas où vous voudriez avoir un semblant de branche, veuillez seulement faire vos builds avec le nom de branche qui suit le nom de votre projet (projet:nombranche). Ça permet au moins de ne pas avoir trop de pollution. La seule chose, si vous effacez la branche, vous devrez effacer manuellement votre “branche” SonarQube. Dans le cas où vous êtes futé, vous opterez pour un script qui synchronise vos branches existantes avec celles visibles sur SonarQube.

NDepend et SonarQube, ça pourrait vous sembler faire le même boulot, mais au final, non. Ces deux outils sont complémentaires. Pour plus de détails sur les règles NDepend, veuillez suivre ce lien.

Configuration AppVeyor

Pour la configuration AppVeyor, vous pouvez suivre le lien qui suit: https://www.ndepend.com/docs/appveyor-integration-ndepend

Configuration SonarQube

Pour la configuration SonarQube, vous pouvez suivre ce lien: https://www.ndepend.com/docs/sonarqube-integration-ndepend

Configuration des deux outils ensemble si vous n’avez rien sur le cloud ou déployé

Parlons maintenant de la configuration du pauvre ;). Vous voulez tout de même tester AppVeyor avec SonarQube, mais vous n’avez pas d’instance SonarQube où que ce soit. Ce n’est pas le plus facile, mais c’est possible. Pour ce faire vous aurez 2 choix que je considère “simple” en utilisant une image Docker:

Choix #1: Utiliser l’image SonarQube avec votre compte Heroku 😉

Choix #2: Utiliser l’image SonarQube en locale

Attardons-nous sur le choix #2. Il est possible en utilisant simplement NGrok (~5 mo) ou alternativement Localhost.run. Une fois configuré, vous pourrez ensuite tester rapidement sans utiliser AppVeyor afin de voir si votre configuration fonctionne. Ensuite, simplement configurer votre build avec AppVeyor.

Concernant SonarQube, n’oubliez pas qu’il vous faudra ajouter le plugin NDepend, sinon vous n’aurez pas accès à la “plus value” de NDepend. Du coup, ça ressemblera à un semblant de ce qui suit si vous n’êtes pas derrière un proxy qui bloque tout.

Prenons par exemple l’image Alpine 7.1 de SonarQube Docker

> docker pull sonarqube:7.1-alpine
> docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube:7.1-alpine

Du coup, vous aurez le port 9000 et 9092 exposé en local. Testez avant de continuer, car le serveur prend un peu de temps a démarrer gracieuseté de Maven ou Java, votre choix. Dans mon cas, ça a pris environs 5 minutes. Le port qui vous intéresse le plus sera le 9000. C’est d’ailleurs celui qui sera exposé avec NGrok. Et concernant ce dernier, n’oubliez pas de récupérer votre Token sur https://dashboard.ngrok.com/auth ;).

  • Une fois SonarQube accessible, veuillez vous connecter en utilisant le compte admin créé par défaut (admin/admin).
  • Créez un token (donnez un nom, du genre AppVeyor) et continuez
  • Choisissez le langage C#/Vb.Net (évidemment)
  • Inscrivez comme clef de projet: MyProject
  • Vous aurez ensuite une commande généré qui ressemblera à:
    • Avant le build: SonarQube.Scanner.MSBuild.exe begin /k:"MyProject" /d:sonar.host.url="http://localhost:9000" /d:sonar.login="686d032bda5d6d5fa204ba5ca21d691179c3e502"
    • Le build en soit: MsBuild.exe /t:Rebuild
    • Une fois le build terminé: SonarQube.Scanner.MSBuild.exe end /d:sonar.login="686d032bda5d6d5fa204ba5ca21d691179c3e502"
  • Vous pouvez exercer la commande en local, pour simple test, mais ce n’est pas nécessaire.
  • Normalement ici, je vous aurais simplement dit d’aller dans l’Administration, la Marketplace et pour terminer chercher le plugin NDepend, mais ce ne sera pas le cas. Le plugin ne fait pas partie de la Marketplace.
    • Du coup, si vous avez ouvert le tutoriel SonarQube sur NDepend, vous aurez vu qu’il faudra installer le plugin manuellement. Le plugin sera disponible à partir du dossier “Integration\SonarQube” de l’archive téléchargée. (sonar-ndepend-plugin-1.1.jar)
      • Note que vous avez accès au bash en effectuant > docker exec -it 70e3d40ba8ea /bin/bash où 70… signifie votre id de conteneur.
    • Copier le fichier plugin NDepend (dans mon cas, windows > Docker) > docker cp d:\Ndepend\NDepend_2018.2.1.9119\Integration\SonarQube\sonar-ndepend-plugin-1.1.jar sonarqube:/opt/sonarqube/extensions/plugins
      • Changez les permissions (sonarqube/sonarqube) du fichier et redémarrer le serveur
    • Vous devriez désormais avoir la section NDepend dans votre SonarQube 
    • On va aller en mode “par défaut”, mais sinon vous pourriez configurer le tout tel qu’indiqué sur le tutoriel. À priori, ce n’est pas nécessaire si vous effectuez votre build proprement avec votre propre fichier ndproj (projet NDepend). N’oubliez simplement pas d’activer les règles (Rules=>C# Language=>NDepend repository => Sélectionnez et configurez)

Maintenant que ça roule, il est désormais le temps de passer aux choses sérieuses. Le configuration NGrok avant le test ultime de Build + Ngrok + SonarQube

> ngrok authtoken 4wUZ8A.................
> ngrok http 9000

ngrok http 9000

C’est bon, votre NGrok est utilisable! L’identité de la redirection n’est pas permanente, donc, ça ne me dérange pas trop de vous montrer les données finales.

Exemple Ngrok + SonarQube

En espérant que vous n’êtes pas trop perdu. Maintenant vous pourrez ajouter à votre build AppVeyor les indications du tutoriel AppVeyor. Excepté que lors de l’exécution du build, vous n’allez pas exécuter la commande NDepend.Console.exe, mais celle du fichier exécutable NDepend.SonarQube.RuleRunner.exe qui se retrouvait là ou vous aviez votre plugin SonarQube. Notez qu’ici vous pouvez aussi garder NDepend.Console.Exe et générer un Artifact, mais cette partie est déjà couverte sous le tuto ;).

Comme SonarQube vous le disait à l’ouverture, vous devrez configurer le pré-build, le build ainsi que le post-build. À partir de ce moment là, vous n’avez qu’à intégrer vos commandes tels qu’indiqués dans le tutoriel SonarQube.

Ajout de l’extension Visual Studio et appréciation

Ce sera l’option la plus simple, et sans doute la plus utilisé par un développeur. Cette option n’est pas toujours possible pour tous les développeurs à cause du coût. Mais ceci-dit,  voici les parties intéressantes de l’extension (Information du site web ou sur le site Microsoft):

  • Dire au développeur avant même de faire ses commit la quantité de dette technique ajoutée
  • Affichage facile des dépendances en utilisant des graph
  • Possibilité de faire des requêtes en live sur le code (CQLinq) pour les passionné de Linq tout particulièrement sur de gros projets
  • Intégration avec VSTS (Team Service)
  • Affichage des trends LoC et bugs en autres
  • Différent affichage lors de différence entre versions (History diff) ou des dettes, etc.

Les promesses:

  • Réduire la dette technique ainsi qu’augmenter les compétences des dévs
  • Rendre le code plus simple à maintenir
  • Analyse des impacts plus simple pour les architectes
  • Plus facile de connaître les coûts lors des demandes de changement (Car les dépendances sont bien visible et clair dans les graphs)

Information importante: Le site de Microsoft n’a pas la dernière mise à jour, mais vous aurez l’extension dans votre archive.

En tant qu’architecte, est-ce que je considère cela utile?

Oui, tout à fait. J’ai souvent à faire du “Reverse Engineering” et cet outil m’aurait sauvé bien des heures à essayer de comprendre comment l’application interagit. Dans un premier temps vous pouvez vous demander pourquoi j’ai eu à faire ça. Je dirais que les applications bien documentés ça va et elle sont rare, cependant, lorsqu’elles ne le sont pas ou du moins personne ne sait vraiment dû à taux de roulement élevé ou lors de développements externes, vous verrez rapidement les bénéfices d’un tel outil. Souvent, on est emmené à sauter dans le code et analyser rapidement ce qui se passe afin de faire un choix stratégique d’architecture et ce n’est pas toujours évident. Si un peu de temps peut être gagné, ça fait déjà ça de gagné afin d’avoir une meilleure analyse.

Un autre cas où ça peut vous être utile sera le suivant. Si vous êtes consultant en .Net (C#), ça peut vous sauver des heures et vous aider à accomplir votre mandat à vitesse grand V. Vous comprendrez rapidement comment l’application s’interconnecte en plus de la qualité générale de l’application. Donc même si le produit n’est pas gratuit, il se repaiera très rapidement. D’ailleurs, en tant que consultant des fois on doit écrire du code rapidement, et il n’y a rien de plus frustrant pour le client si votre code ajoute de la dettes technique. Pensez-y ;).

Performances

Vous avez un PC qui date un peu, est-ce que ça fonctionnera dans Visual Studio sans détruire votre vie, où l’attente est interminable? D’ailleurs c’est un des soucis que j’ai avec Resharper (JetBrains) à l’ouverture de gros projets. Le temps d’analyse est long, et même sur une bonne machine, je réussi à obtenir un message de désactiver des plugins ou de changer les options R#.

À priori, l’extension ne peut pas être pire que celle de JetBrains (R#) en terme de consommation ou lenteur. R# s’est vraiment amélioré avec le temps, mais c’est encore un soucis sur mon vieux laptop. Je dirais ici que NDepend s’intègre plus simplement (moins d’options cachée) et est moins gourmand (Facteur 10x).

La question qui tue, est-ce que ça vaut la peine?

À l’époque j’avais adoré, et je crois que ça vaut toujours la peine d’avoir au moins une licence serveur si ce n’est que de connecter à SonarQube. Malgré la version développeur intéressante, je ne crois pas que ça s’applique à tous. Je vois plus soit les seniors qui doivent faire des études d’impacts, soit les juniors qui doivent s’améliorer à l’aide d’un tel outil. Ensuite, pour ceux qui sont dans le “médian”, ce n’est pas nécessairement intéressant, car ils n’utiliseront pas l’outil, ou du moins pas assez pour que ce soit intéressant par rapport au prix.

Les choses qui rendrait ça encore plus intéressant et qui sait, existeront peut-être bientôt

Ici, je vais tout simplement faire une liste de ce que j’aimerais.

  • Création d’un plugin listé dans SonarQube Marketplace qui permettrait des mises à jours ou une installation plus simple
  • Pouvoir exécuter NDepend à l’intérieur d’un environnement Linux ou pas trop loin (Mac OS à désormais aussi Visual Studio professionnel et ça serait un +). Avec l’arrivée de dotnet core, je vois bien un sonar-runner sous linux pour du code c#.
  • Intégration avec SonarCloud (si c’est possible dans le futur, mais étant donné qu’il faut installer un plugin, ce ne sera pas possible. Par contre, j’ai vu dernièrement qu’il y avait moyen de pousser des erreurs dans SonarQube qui ne viennent pas des QualityGate officielles)
  • Possibilité de corriger les erreurs banales à partir de “VisualNDepend” (Un peu comme si l’on effectuait eslint –fix qui corrige automatiquement les espacements erronées, les accolades manquantes, les lignes trop longues découpées sur plusieurs lignes, etc.)
  • Possibilité d’inclure facilement dans un build GibLab (de plus en plus populaire) ou Bamboo (très utilisé dans l’industrie de nos jours)
  • À partir de VisualNDepend, avoir une capacité de s’intégrer à Git et à reculer dans le temps (machine à voyager dans le temps). Par exemple, de dire je veux une baseline à partir du mois de Janvier 2015 et j’aimerais avoir les statistiques par mois jusqu’à aujourd’hui. Ça prendrait du temps, cependant, ça permettrait de voir dans quel direction les développements s’en vont et à quel moment certains points critiques (plusieurs dépendances) se sont développées. Ce serait vraiment une superbe fonctionnalité à avoir. Le plugin Visual Studio permet d’avoir une idée générale, mais ça, ça pousserait l’idée plus loin.

Article Sponsorisé

Merci à l’équipe NDepend de m’avoir approché afin d’écrire un article sur leur produit. Je n’avais pas eu l’opportunité d’utiliser leur produit depuis un moment et je suis bien heureux de voir les progrès fait par ce logiciel.

]]>
https://blog.honosoft.com/2018/11/05/ndepend-sonarqube-ou-pourquoi-pas-les-integrer-ensemble-%f0%9f%98%89/feed/ 1
Dotnet Core et ses templates CLI, pourquoi s’arrêter à C# ou peut-être pas? https://blog.honosoft.com/2018/10/30/dotnet-core-et-ses-templates-cli-pourquoi-sarreter-a-c-ou-peut-etre-pas/?utm_source=rss&utm_medium=rss&utm_campaign=dotnet-core-et-ses-templates-cli-pourquoi-sarreter-a-c-ou-peut-etre-pas https://blog.honosoft.com/2018/10/30/dotnet-core-et-ses-templates-cli-pourquoi-sarreter-a-c-ou-peut-etre-pas/#respond Tue, 30 Oct 2018 13:06:54 +0000 https://blog.honosoft.com/?p=300 Continue Reading]]> Comme le titre l’indique, pourquoi s’arrêter à un template et pourquoi devrait-on, ou pas, faire que des templates C#?

Afin de répondre à cette question, il faut tout d’abord créer un template ou s’inspirer d’un existant. Notez qu’il est possible de créer des packets contenant plusieurs templates à la fois.

Comment créer un template .Net (core ou pas, c’est pas grave)

Pour débuter, on va faire ça simple. Créons un petit template C# dotnet avec rien de bien intéressant à l’intérieur a part peut être un pre-setting pour un album photo (Connection BD + Script de migration entity framework). Dans cet article, nous n’allons pas bâtir l’application, mais seulement expliquer les principes et le packaging.

Par exemple, vous pouriez faire dotnet new mvcet terminer le reste de votre projet de base (template). Ensuite, il afin de transformer ce projet en template, vous aurez à créer un répertoire spécial nommé .template.config qui se situe dans votre répertoire de projet. Ce fichier respecte doit respecter les standards Microsoft (disponible sur le site MSDN si vous cherchez un peu).

Ici par exemple vous pouriez avoir ce qui suit (Vous pouvez adapter à bon vous semble ;)):

{
  "$schema": "http://json.schemastore.org/template",
  "author": "Nordes Menard-Lamarre",
  "classifications": [ "MVC", "PhotoGallery" ],
  "groupIdentity": "HoNoSoFt.DotNet.Web.Templates.Gallery",
  "identity": "HoNoSoFt.Gallery",
  "name": "Ce qui s'affiche dans la description",
  "preferNameDirectory": true,
  "primaryOutputs": [
    {
      "path": "HoNoSoFt.Gallery.csproj"
    }
  ],
  "shortName": "honosoft-gallery",
  "sourceName": "HoNoSoFt.Gallery",
  "sources": [
    {
      "source": "./",
      "target": "./",
      "exclude": [
        ".template.config/**"
      ],
      "modifiers": [
        {
          "condition": "(TargetFrameworkOverride == '')",
          "exclude": [
            "app.config"
          ]
        },
        {
          "condition": "(ExcludeLaunchSettings)",
          "exclude": [
            "Properties/launchSettings.json"
          ]
        }
      ]
    }
  ],
  "symbols": {
    "ExcludeLaunchSettings": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "Whether to exclude launchSettings.json from the generated template."
    },
    "HttpPort": {
      "type": "parameter",
      "datatype": "integer",
      "description": "Port number to use for the HTTP endpoint in launchSettings.json."
    },
    "HttpPortGenerated": {
      "type": "generated",
      "generator": "port"
    },
    "HttpPortReplacer": {
      "type": "generated",
      "generator": "coalesce",
      "parameters": {
        "sourceVariableName": "HttpPort",
        "fallbackVariableName": "HttpPortGenerated"
      },
      "replaces": "8080"
    },
    "HttpsPort": {
      "type": "parameter",
      "datatype": "integer",
      "description": "Port number to use for the HTTPS endpoint in launchSettings.json. This option is only applicable when the parameter no-https is not used (no-https will be ignored if either IndividualAuth or OrganizationalAuth is used)."
    },
    "HttpsPortGenerated": {
      "type": "generated",
      "generator": "port",
      "parameters": {
        "low": 44300,
        "high": 44399
      }
    },
    "HttpsPortReplacer": {
      "type": "generated",
      "generator": "coalesce",
      "parameters": {
        "sourceVariableName": "HttpsPort",
        "fallbackVariableName": "HttpsPortGenerated"
      },
      "replaces": "44300"
    },
    "TargetFrameworkOverride": {
      "type": "parameter",
      "description": "Overrides the target framework",
      "replaces": "TargetFrameworkOverride",
      "datatype": "string",
      "defaultValue": ""
    },
    "Framework": {
      "type": "parameter",
      "description": "The target framework for the project.",
      "datatype": "choice",
      "choices": [
        {
          "choice": "netcoreapp2.1",
          "description": "Target netcoreapp2.1"
        }
      ],
      "replaces": "netcoreapp2.1",
      "defaultValue": "netcoreapp2.1"
    },
    "HostIdentifier": {
      "type": "bind",
      "binding": "HostIdentifier"
    },
    "skipRestore": {
      "type": "parameter",
      "datatype": "bool",
      "description": "If specified, skips the automatic restore of the project on create.",
      "defaultValue": "false"
    },
    "NoHttps": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "Whether to turn off HTTPS. This option only applies if Individual, IndividualB2C, SingleOrg, or MultiOrg aren't used for --auth."
    }
  },
  "tags": {
    "language": "C#",
    "type": "project"
  },


  "precedence": 100,
  "guids": [
      "6B3E8EE8-92E3-41CE-A356-A5F880EEA367"
  ],
  "postActions": [
      {
        "condition": "(!skipRestore)",
        "description": "Restore NuGet packages required by this project.",
        "manualInstructions": [
          {
            "text": "Run 'dotnet restore'"
          }
        ],
        "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
        "continueOnError": true
      }
    ]
}

Une fois ce fichier sauvegardé, ce n’est pas terminé. Vous devrez créer votre fichier “nuspec”. C’est possible de le créer à partir de Visual Studio, ou si vous avez la flemme, de le créer aussi manuellement. Dans tous les cas, vous devrez télécharger une version de nuget en ligne de commande.

Exemple de fichier nuspec (Aussi très bien documenté sur le site de MSDN):

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <id>HoNoSoFt.DotNet.Web.Spa.ProjectTemplates</id>
    <authors>Nordes Menard-Lamarre</authors>
    <description>
      La description qui apparaitra sur le site de Nuget.org
    </description>
    <packageTypes>
      <packageType name="Template" />
    </packageTypes>
    <version>{{version}}</version>
    <iconUrl>https://www.honosoft.com/img/logo.png</iconUrl>
    <projectUrl>https://github.com/votre_nom/Votre.ProjectTemplates</projectUrl>
    <licenseUrl>https://github.com/votre_nom/Votre.ProjectTemplates/blob/master/LICENSE</licenseUrl>
  </metadata>
  <files>
    <file
      src="HoNoSoFt.Gallery.CSharp/**/*"
      exclude="**/node_modules/**;**/package-lock.json;**/bin/**;**/obj/**;**/.vs/**;**/.vscode/**;**/wwwroot/**"
      target="Content" />
  </files>
</package>

Ici concernant les fichiers, vous n’être absolument pas obligé d’avoir une configuration fichier par template. Si vous voulez vous simplifier la vie, vous pouvez simplement vous créer un répertoire “Contenu” et y mettre vos projets templates en C#. Ceci-dit, une fois complété, pour créer votre package “nupkg“, vous devrez lancer la ligne de commande avec nuget.

La commande est (à partir de votre répertoire nuspec) : nuget pack. Avant de lancer la commande, n’oubliez pas de mettre à jour votre numéro de {{version}}. Cette version doit être sous le standard Majeur.Mineur.Build.

Afin de tester votre template vous pourrez ensuite l’installer en effectuant: dotnet new -i votre.fichier.nupkg. Et pour voir si c’est bien installé, n’oubliez pas que vous pouvez aussi lister votre projet à l’aide de la commande dotnet new -l. Une fois que vous êtes certain que votre template est présent, vous pouvez créer un répertoire “temporaire” et tester l’initialization de votre template. La commande cette fois sera dotnet new votre-nom-de-templateet les options par défauts seront utilisées (Voir le template.json créer plus tôt).

Et si on veut ajouter d’autres templates en même temps?

Oui, c’est possible. Vous pouvez soit ajouter d’autre répertoire dans la liste des fichiers (balise files qui est dans le nuspec) ou bien si vous avez opté pour un répertoire plus global, comme “Contenu”, vous n’avez qu’à créer d’autres projets avec le répertoire .template.config. Cependant, il y a un point important. Et ce point est que vous devez impérativement avoir un nom différent dans le GroupIdentity du fichier template.json. Sinon, si vous voulez utiliser le même nom, vous devrez combiner avec des options (voir les détails du schéma)

Est-ce possible de créer des template non C# ou Microsoft

Je crois que c’est tout à fait possible et ce sera sans doute ce que je ferai dans l’avenir. J’aime bien travailler avec node pour des PoC et je crois que d’utiliser dotnet new avec les templates va faciliter le fait d’utiliser ou non un backend.

Exemple réel

Récemment, j’ai travailler sur un projet personnel qui crée un Template Dotnet Core 2.1 + VueJS + Picnic CSS + beaucoup d’autres trucs. Ce template est disponible sur https://github.com/nordes/HoNoSoFt.DotNet.Web.Spa.ProjectTemplates et vous pouvez aussi voir comment j’ai intégrer le tout avec AppVeyor pour les build automatisé et le déploiement automatique de l’artifact NuGet sur nuget.org.

Tous les détails afin d’utiliser les templates sont disponible sur le site Github ou bien sur la section Wiki de Github.

Bon coding!

Nordès

]]>
https://blog.honosoft.com/2018/10/30/dotnet-core-et-ses-templates-cli-pourquoi-sarreter-a-c-ou-peut-etre-pas/feed/ 0
Être notifié lors d’un changement de contenue de fichier https://blog.honosoft.com/2018/10/22/etre-notifie-lors-dun-changement-de-contenue-de-fichier/?utm_source=rss&utm_medium=rss&utm_campaign=etre-notifie-lors-dun-changement-de-contenue-de-fichier https://blog.honosoft.com/2018/10/22/etre-notifie-lors-dun-changement-de-contenue-de-fichier/#respond Mon, 22 Oct 2018 14:22:30 +0000 https://blog.honosoft.com/?p=291 Continue Reading]]> Il arrive souvent qu’on aille des fichiers de configuration pour nos application. Lorsqu’on fait fait une modification, on doit repartir l’application. Donc, le mieux serait d’avoir un mécanisme qui reload les configurations au “runtime“.

FileSystemWatcher

Il y  a déjà plusieurs articles de disponible sur cette classe. Donc je n’irai pas dans les détails. Le concept est assez simple: on donne un répertoire et on s’abonne aux évènements.

private void Init() {
    FileSystemWatcher watcher = new FileSystemWatcher();
    watcher.Path = "path/you/want/to/watch";
    watcher.Filter = "*.json"; // if you want to watch only 1 file, write the filename here
    watcher.EnableRaisingEvents = true; // Don't forget this, or else it won't start the watcher.

    watcher.Changed += new FileSystemEventHandler(OnChanged);
}

// Define the event handlers.
private void OnChanged(object source, FileSystemEventArgs e)
{
    // Specify what is done when a file is changed, created, or deleted.
   Console.WriteLine("File: " +  e.FullPath + " " + e.ChangeType);
}

Ça fonctionne bien généralement. Par contre, si vous voulez utiliser ça dans votre projet .Net, vous risquez d’avoir une mauvaise surprise. Pour une raison de sécurité des fichiers, Visual Studio crée un fichier temporaire si un fichier de votre projet est utilisé. Il devient donc impossible de “watcher” un fichier spécifique, comme le nom et l’extension seront perdu quand le “OnChanged” sera appelé.

Donc, il faut se tourner vers une méthode alternative.

System.Web.Caching

Si vous avez déjà fait du ASP.Net, il est for probable que vous ailliez déjà travailler avec le système de cache par défaut. Ce qui est bien avec cette cache, c’est qu’on peut associer un fichier avec sa valeur dedans. Voici un exemple simple:

public string AddItemToCache(string filename) {
    var content = File.ReadAllText(filename);
    Cache.Add("Key1", 
        content, 
        new CacheDependency(fileName), 
        DateTime.Now.AddSeconds(60), 
        Cache.NoSlidingExpiration, 
        CacheItemPriority.High);

    return content;
}

public string GetConfig() {
    var content = Cache.Get("Key1");
    if (content == null)
        content = AddItemToCache("file.config");

    content content;
}

Conclusion

J’ai écrit cet article parce que j’ai perdu beaucoup de temps pour rien avec le FileSystemWatcher pour réussir à comprendre la limitation. Donc, si vous avez d’autres alternatives, n’hésitez pas à m’en faire part, il me fera plaisir de l’ajouter à l’article.

]]>
https://blog.honosoft.com/2018/10/22/etre-notifie-lors-dun-changement-de-contenue-de-fichier/feed/ 0