Skip to content

Use objects and structs as the type of the argument

Giuseppe Cramarossa edited this page Nov 27, 2023 · 3 revisions

You can define arguments of any type, including custom objects.

In this page you will see how to

  • use existing .net objects as argument
  • use the CustomInputString attribute
  • create a custom object and use it as argument
  • implememt it throught an example

How to define objects and structures as command arguments

InterAppConnector provides the possibility to use some object types that already exists in .net without writing custom code. In particulr, the library checks if there is a constructor that accepts only one argument of type string. If the constructor exists, the value assigned to the argument is passed to the constructor. Some examples of types that you can use without writing custom code are Guid and DateTime.

Let's assume that you want to define an argument that should be a valid GUID. All you have to do in the code is to define a property of type Guid, as showed in the code below

public Guid Uid {get; set;}

Guid has already a constructor that accepts a string as paramter and throws an exception if the string is not a valid GUID

If the object raises an exception, InterAppConnector consider the value passed to the argument not valid and will raise an error.

Is it also possible to check if the value respects a particular format. This is very useful if the data may vary according to user settings. An example is the date an time validation, where the data format vary between countres to and you want to define a standard one. In this case, you can add the CustomInputString attribute to the property. This attribute accepts a string as parameter, that is the format you want to use for that particular argument

Let's assume that you want to define an argument that should be a valid date. Define your argument as written in the code below

[CustomInputString("ddMMyyyy")]
public DateTime Date {get; set;}

When you define a custom input string as argument, the library will search for a method called ParseExact that accepts three parameters:

  • the value of the argument
  • the format
  • the format provider

and returns the object.

Then, the library pass the value of the argument as first parameter, the format defined as string in the CustomInputString as second parameter and set the last one to CultureInfo.InvariantCulture. The last parameter is hardcoded, and actually it is not possible to change it

In custom objects, you have to define your own ParseExact method.

For instance, if you want to validate the value inserted as license plate you have to add a method in your class as described below (let's assume that the class name is LicensePlate)

public class LicensePlate 
{
    public static LicensePlate ParseExact(string value, string format, IFormatProvider culture)
    {
        // write your validation code here
    }
}

Use CustomInputString attribute in order to change the default behaviour

Previously, it is said that the library checks for a constructor or a specific method in order to pass the parameter to the object.

However, it is possible to define a custom method that can be used in alternative to the constructor.

In order to use this method, you have to add the CustomInputString attribute to your method. There should be only one argument of type string and you can choose the name of the method. Remember also that the method should be static and must return itself

There should be only one method with the CustomInputString attribute in your class, otherwise the library will raise a DuplicateObjectException exception.

For example, consider the following code:

public class Example 
{
    public int Number {get; set;}

    public Example (string number)
    {
        Number = 5;
    }

    [CustomInputString]
    public static Example SetCustomNumber (string number)
    {
        Example example = new Example();
        example.Number = 15;
        return this;
    }   
}

If you want to define a property called UserNumber of type Example and use it as argument of your application, the value assigned to Number will be 15. If you delete the CustomInputString attribute from the SetCustomNumber method the value assigned to Number will be 5

You can also add your ParseExact method if you want to parse your string in the same way described above

Define mandatory and optional arguments of type object

It is also possible to define optional and mandatory arguments. However there is a slight difference respect to other types. InterAppConnector consider nullable objects as optional arguments. So if the type is passed by value (for instance structures, numbers, booleans and so on) is nullable, the argument is optional, otherwise it is mandatory. For example if you want to define as arguments two numbers, one mandatory an one optional you have to write the following code:

public int MandatoryNumber {get; set;}
public int? OptionalNumber {get; set;}

The first argument, mandatorynumber is mandatory because the property MandatoryNumber does not accept null values, while the last one does, so it is optional.

Object types are passed by reference, so they can accept null values. For this reason it is not possible to use the method described above. Instead, if you want to define a mandatory argument, you have to assign a value to the respective property, otherwise it is considered as optional.

For example if you want to define as arguments two numbers, one mandatory an one optional you have to write the following code:

public string MandatoryString {get; set;} = string.Empty;
public string OptionalString {get; set;}

The first argument, mandatorystring is mandatory because the property MandatoryString is not null, while the last one has its default value, null, so it is optional.

Example

In this example, you are creating a simple user management application that has two commands

  • CreateUserCommand
  • DeleteUserCommand

These commands have two arguments:

  • usercode : contains the user code. This argument is mandatory for both commands
  • name : contains the user name. This argument is mandatory in user creation and mandatory in user deletion.

For this example you are using Visual Studio 2022 and you are creating:

  • A project library that contains the commands and a file containing the arguments necessary to run the application
  • A console application that will be used to call the command via cli

For this example it is assumed that you have learned what is a command and how to implement it

Follow these steps to complete this example:

  1. Open Visual Studio, create a blank solution and name it UserManager. Choose the language version (.NET 6 or above) and choose a location for your solution (for example C:\SampleProjects)

  2. Create a new library project and call it UserManager.Library. Then follow the steps below to complete the class library

    1. Add the InterAppConnector library as project reference

    2. Add a file to the project and call it UserManagerDataModel.cs

    3. Replace the content of UserManagerDataModel.cs with the code below

      namespace UserManager.Library
      {
          public class UserManagerDataModel
          {
              public string UserCode { get; set; } = string.Empty;
              public string FileLocation { get; set; }
              public string Name { get; set; }
          }
      }
    4. Rename Class1.cs to CreateUserCommand.cs and create two new files called DeleteUserCommand.cs and LicensePlate.cs. Change the visibility in the classes from internal to public

    5. In LicensePlate.cs, replate the content of the file with the code below

      using System.Text.RegularExpressions;
      
      namespace UserManager.Library
      {
          public class LicensePlate
          {
              private string _plate = "";
              public string Plate
              {
                  get
                  {
                      return _plate;
                  }
              }
      
              public LicensePlate(string plate)
              {
                  _plate = plate;
              }
      
              public static LicensePlate ParseExact(string value, string format, IFormatProvider culture)
              {
                  LicensePlate plate;
                  string licensePlate = "";
      
                  if (format.Length == value.Length)
                  {
                      string loweredFormat = format.ToLower();
                      for (int i = 0; i < value.Length; i++)
                      {
                          switch (loweredFormat[i])
                          {
                              case 'l':
                                  if (Regex.IsMatch("" + value[i], @"[a-zA-Z]"))
                                  {
                                      licensePlate += value[i];
                                  }
                                  else
                                  {
                                      throw new FormatException("Character " + i + ": Wrong character '" + value[i] + "'. It should be a letter");
                                  }
                                  break;
                              case 'n':
                                  if (Regex.IsMatch("" + value[i], @"[0-9]"))
                                  {
                                      licensePlate += value[i];
                                  }
                                  else
                                  {
                                      throw new FormatException("Character " + i + ": Wrong character '" + value[i] + "'. It should be a number");
                                  }
                                  break;
                              case 'x':
                                  if (Regex.IsMatch("" + value[i], @"[a-zA-Z0-9]"))
                                  {
                                      licensePlate += value[i];
                                  }
                                  else
                                  {
                                      throw new FormatException("Character " + i + ": Wrong character '" + value[i] + ". It should be an alphanumeric character");
                                  }
                                  break;
                              default:
                                  throw new FormatException("Character " + i + ": Unrecognised character in format. The allowed characters are l for letters, n for numbers and x for alphanumerical characters");
                          }
                      }
                  }
                  else
                  {
                      throw new FormatException("Value and format must have the same length");
                  }
      
                  plate = new(licensePlate);
                  return plate;
              }
          }
      }
    6. Replace the content of UserManagerDataModel.cs with the code below

      using InterAppConnector.Attributes;
      
      namespace UserManager.Library
      {
          public class UserManagerDataModel
          {
              public Guid UserCode { get; set; } = Guid.NewGuid();
      
              public FileInfo FileLocation { get; set; } = new FileInfo(@".\");
      
              [MandatoryForCommand(typeof(CreateUserCommand))]
              [CustomInputString("lllnnnn")]
              public LicensePlate LicensePlate { get; set; }
          }
      }
    7. Replace the content of CreateUserCommand.cs with the code below

      using InterAppConnector;
      using InterAppConnector.Attributes;
      using InterAppConnector.Interfaces;
      using System.Text.RegularExpressions;
      
      namespace UserManager.Library
      {
          [Command("create", Description = "Create a user in a file")]
          public class CreateUserCommand : ICommand<UserManagerDataModel>
          {
              public string Main(UserManagerDataModel arguments)
              {
                  string message = "";
                  bool userAlreadyExists = false;
      
                  if (arguments.FileLocation.Exists)
                  {
                      List<string> userList = new List<string>(File.ReadLines(arguments.FileLocation.FullName));
                      int rowsFound = (from item in userList
                                          where item.ToLower().StartsWith(arguments.UserCode.ToString() + ",")
                                          select item).Count();
      
                      if (rowsFound > 0)
                      {
                          userAlreadyExists = true;
                      }
                  }
      
                  if (userAlreadyExists)
                  {
                      message = CommandOutput.Warning("The user code already exists. No row was added");
                  }
                  else
                  {
                      File.AppendAllText(arguments.FileLocation.FullName, arguments.UserCode.ToString() + "," + arguments.LicensePlate.Plate + Environment.NewLine);
                      message = CommandOutput.Ok("The user was added to the list");
                  }
      
                  return message;
              }
          }
      }
    8. Replace the content of DeleteUserCommand.cs with the code below

      using InterAppConnector;
      using InterAppConnector.Attributes;
      using InterAppConnector.Interfaces;
      using System.Text.RegularExpressions;
      
      namespace UserManager.Library
      {
          [Command("delete", Description = "Delete a user in a file")]
          public class DeleteUserCommand : ICommand<UserManagerDataModel>
          {
              public string Main(UserManagerDataModel arguments)
              {
                  string message = "";
      
                  if (Regex.IsMatch(arguments.UserCode.ToString(), @"^[a-z0-9-]+$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(1000)))
                  {
                      if (arguments.FileLocation.Exists)
                      {
                          string name = "";
                          bool isNameValid = true;
                          List<string> userList = new List<string>(File.ReadLines(arguments.FileLocation.FullName));
                          List<string> rowsFound = new List<string>();
      
                          if (!string.IsNullOrEmpty(arguments.LicensePlate.Plate))
                          {                    
                              if (Regex.IsMatch(arguments.LicensePlate.Plate, @"^[a-z0-9]+$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(1000)))
                              {
                                  name = arguments.LicensePlate.Plate;
                                  rowsFound = (from item in userList
                                              where item.ToLower() == arguments.UserCode.ToString().ToLower() + "," + name.ToLower()
                                              select item).ToList();
                              }
                              else
                              {
                                  isNameValid = false;
                              }
                          }
                          else
                          {
                              rowsFound = (from item in userList
                                          where item.ToLower().StartsWith(arguments.UserCode.ToString().ToLower() + ",")
                                          select item).ToList();
                          }
      
                          if (isNameValid)
                          {
                              
                              if (rowsFound.Count > 0)
                              {
                                  userList.Remove(rowsFound[0]);
                                  File.WriteAllLines(arguments.FileLocation.FullName, userList);
                                  message = CommandOutput.Ok("User deleted successfully");
                              }
                              else
                              {
                                  message = CommandOutput.Warning("The user does not exist. No rows was deleted");
                              }
                          }
                          else
                          {
                              message = CommandOutput.Error("The name contains invalid characters. Only letters and spaces are allowed");
                          }
      
                      }
                      else
                      {
                          message = CommandOutput.Error("The file does not exist");
                      }
                  }
                  else
                  {
                      message = CommandOutput.Error("The user code contains invalid characters. Only letters and numbers are allowed");
                  }
      
                  return message;
              }
          }
      }
  3. Create a new console application project and name it UserManager.CLI. Then follow the steps below to complete the console application:

    1. Add the InterAppConnector library as project reference

    2. Add the UserManager.Library project as project reference

    3. Replace the content of Program.cs with the code below (CommandManager and InterAppConnector are explained in detail in Integration with console applications page)

      using InterAppConnector;
      using UserManager.Library;
      
      namespace UserManager.CLI
      {
          public class Program
          {
              static void Main(string[] args)
              {
                  CommandManager commandManager = new CommandManager();
                  commandManager.AddCommand<CreateUserCommand, UserManagerDataModel>()
                      .AddCommand<DeleteUserCommand, UserManagerDataModel>();
      
                  InterAppCommunication interAppCommunication = new InterAppCommunication(commandManager);
                  interAppCommunication.ExecuteAsInteractiveCLI(args);
              }
          }
      }
  4. Build the solution (you can leave the debug mode if you want)

  5. Open the Command Prompt and go to the location of the executable. For instance, if you have placed your solution in C:\SampleProjects you can go to the executable by typing the above command

    cd C:\SampleProjects\UserManager\UserManager.CLI\bin\Debug\net6.0
  6. Type the executable name with the extension (for instance UserManager.CLI.exe). You should see an output similar to the one below

    ----------------------------------------------------------------------------------
    
    UserManager.CLI 1.0.0.0
    
    ----------------------------------------------------------------------------------
    
    Available actions:
    
    - create
    Create a user in a file
            -usercode (required) : No description provided
            -filelocation (required) : No description provided
            -licenseplate (required) : No description provided
    
    - delete
    Delete a user in a file
            -usercode (required) : No description provided
            -filelocation (required) : No description provided
            -licenseplate (optional) : No description provided
  7. Create a new user and try to insert a wrong guid or file path. For instance, type UserManager.CLI.exe create -usercode xxx -location .\addresses.txt. You should see this error

    [25/11/2023 16:29:06] ERROR (3) : The value provided to argument usercode is not acceptable. Reason: Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). (Parameter 'usercode')
  8. If you type all mandatory parameters and are in the correct format, the user will be added in the list