Wednesday, 24 August 2022

Automapper

The Automapper is a great utility that will easily facilitate the conversion of models to other models without needing to  define that logic inside the models themselves. It's a simple Nuget package that allows you to create "Profiles" which more or less define rules on how to convert one Type of object to another, so for example if you had an HourlyEmployee class and you wanted to convert it to a SalaryEmployee Class.

More often than not the auto mapper is actually used to generate Data Transfer Objects (DTOs) for APIs, DTOs are generally a manipulation of a model either to provide consumers of the API a subset and/or a manipulation of a model.

To demonstrate the auto mapper we are going to build a very simple Console Application.

To Get started let's set up our Console application

  1. mkdir pav.automapper.cnl  create a directory for your project
  2. cd pav.automapper.cnl navigate into that directory
  3. dotnet new console --use-program-main insatiate a console application 
  4. code . open the folder in MS code
You're MS Code should open with your current working directory open, if you are prompted to trust yourself, go ahead and do that.


with your project trusted, before we get started ensure that you have the C# extension from Microsoft installed.


On the left panel click the Extensions button and make sure that you have the C# extension added, once you are sure that you have it, click on the explorer button and open you project file, it's the one that ends in .csproj


with that open we are now going to add a nuget package for automapper 
https://www.nuget.org/packages/automapper/
if you are looking to add this to an API then rather than install just automapper, install the Microsoft dependency injection variant of it

for now let's add the basic automapper nuget with 

dotnet add package automapper


with our package added notice that our project file now makes a reference to it


next let's run our our project with "dotnet run" 


exactly what we would expect to see, simply "Hello World!" written out to our console. let's open up our program.cs file and see what we are starting with.


not much there, and to be honest if we didn't include our "-use-program-main" flag when we created our application it would be even less, it would look something like:


which to me is just confusing, but that's probably cause I'm old now, by software standards I should have died of a stroke years ago.

To get start we are going to need some models, were going to keep it pretty simple and create a
simple Employee class and a corresponding salaryEmployee and hourlyEmployee class definitions which inherit from an Abstract Employee, then after we are going to use automapper to convert between the two.

so first lets get started by setting up our models folder, with our three classes defined


Now whenever I make an abstract class I like to prefix it with the word Base, just to give me a visual que that it's an abstract class and that any other class with the word employee in it will derive from that base class, it's just a personal preference, you can name them whatever you like.

lets start by defining our Base Employee class, one thing to remember is that all of our classes that we will be converting to, require a parameterless constructor, so that auto mapper can instantiate them.

using System;

namespace pav.automapper.cnl.Models{
    public abstract class BaseEmployee{
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
        public DateTime Birthdate { get; set; }

        public BaseEmployee(){}
        public BaseEmployee(string firstName, string lastName, DateTime birtdate){
            this.FirstName = firstName;
            this.LastName = lastName;
            this.Birthdate = birtdate;
        }

        public abstract float payMoney();

        public int getAge()
        {
            var today = DateTime.Today;
            var age = today.Year - Birthdate.Year;
            if (Birthdate.Date > today.AddYears(-age))
                return age-1;

            return age;
        }

        public override string ToString()
        {
            var fn = this.FirstName;
            var ln = this.LastName;
            var age = this.getAge();
            return $"{fn} {ln} is {age} years old";
        }
    }
}

Pretty simple class definition we overrode the ToString() method to make it easier for us to later output to our console and we create an abstract payMoney function which will be used to calculate our employees wages.

Next let's define the hourlyEmployee class.

using System;

namespace pav.automapper.cnl.Models{
    public class HourlyEmployee : BaseEmployee{
        public float Wage { get; set; }
        public float Hours { get; set; }

        public HourlyEmployee(){}
        public HourlyEmployee(string firstName, string lastName, DateTime birthdate, float wage, float hours)
            :base(firstName, lastName, birthdate){
                this.Wage = wage;
                this.Hours = hours;
        }

        public override float payMoney(){
            return Hours * Wage;
        }

        public override string ToString()
        {
            var baseString = base.ToString();
            return $"{baseString} and will be paid ${payMoney()}";
        }
    }
}

again no rocket science here, we implemented to payMoney function as well as overrode the ToString method.

One more Model to go and that's the SaleryEmployee one.

using System;

namespace pav.automapper.cnl.Models{
    public class SalaryEmployee : BaseEmployee{
        public float Salary { get; set; }
       
        public SalaryEmployee(){}
        public SalaryEmployee(string firstName, string lastName, DateTime birthdate, float salary)
            :base(firstName, lastName, birthdate){
                this.Salary = salary;      
        }
       
        public override float payMoney(){
            return Salary;
        }

        public override string ToString()
        {
            var baseString = base.ToString();

            return $"{baseString} and has a salary of ${payMoney()}";
        }
    }
}

More or less the same as before if not simpler since we removed the hour and wage and just provide a base salary, we also updated the two String method to make it easier to distinguish between the two types of employees.

let's go to our Program.cs class and test our our models.

using pav.automapper.cnl.Models;
using System;

namespace pav.automapper.cnl
{
    class Program
    {
        static void Main(string[] args)
        {
            var john = new HourlyEmployee("John", "Doe", new DateTime(1984, 1, 31), 10, 40);
            Console.WriteLine(john);

            var bob = new SalaryEmployee("Bob", "Smith", new DateTime(1984, 1, 31), 600);
            Console.WriteLine(bob);
        }
    }
}

we instantiated two different employees john and bob then we write them out to the console, 


exactly what we would expect to see.

We can finally bring in automapper, let's say that we wanted to convert an hourly employee to a salaried one, well we could manually do it, but automapper will let us define mapping rules and do it for us. 

let's get started by creating a Profiles folder and adding an EmployeeProfile class.

We included a base implementation for the EmployeeProfile class and inherited from the Profile class which is included in the AutoMapper nuget.

Let's now define some mapping rules to convert an hourly employee to a salaried one

CreateMap<HourlyEmployee, SalaryEmployee>()
    .ForMember(dest => dest.Salary, opt => opt.MapFrom(src => src.Hours * src.Wage));

it's that simple, we map our destination property to what we want it to be from our source class. the finished EmployeeProfile class should look something like 

using AutoMapper;
using pav.automapper.cnl.Models;
namespace pav.automapper.cnl.Profiles
{
    public class EmployeeProfile : Profile
    {
        public EmployeeProfile()
        {
            CreateMap<HourlyEmployee, SalaryEmployee>()
                .ForMember(dest => dest.Salary, opt => opt.MapFrom(src => src.Hours * src.Wage));
        }
    }
}

Obviously we have to somehow call this, but the conversion logic is done.

let's go back to our Program.cs class and implement our mapper. Let's tart by configuring our automapper.

var config = new MapperConfiguration(cfg => cfg.AddProfiles(new[] { new EmployeeProfile() }));
var mapper = new Mapper(config);

with that done we can now easily convert hourly employees to salaried ones

using AutoMapper;
using pav.automapper.cnl.Models;
using pav.automapper.cnl.Profiles;
using System;

namespace pav.automapper.cnl
{
    class Program
    {
        static void Main(string[] args)
        {
            var config = new MapperConfiguration(cfg => cfg.AddProfiles(new[] { new EmployeeProfile() }));
            var mapper = new Mapper(config);

            var john = new HourlyEmployee("John", "Doe", new DateTime(1984, 1, 31), 10, 40);
            Console.WriteLine(john);

            var bob = new SalaryEmployee("Bob", "Smith", new DateTime(1984, 1, 31), 600);
            Console.WriteLine(bob);

            var John = mapper.Map<SalaryEmployee>(john);
            Console.WriteLine(John);
        }
    }
}

it's just that easy now, we simply run john through our mapper and specify the output class and the rules we defined in our profile will take care of the conversion, any properties that have the same name will be automatically mapped.

Obviously there is a lot more to this, but at it's core that's all the automapper is for, to convert one class and abstract away the mappings for this conversion from the actual classes being converted.

Sunday, 30 January 2022

local mock data for SPA web service

Whenever I build SPA's in vue I use a poor mans IOC pattern, which in principle accomplishes leveraging a mock version of my service that instead of fetching data from a web api leverages local json files, however in practice it has very little to do with Inversion of control, though it could adhere to IOC principles I find my pragmatic approach less hassle. 

I start with creating env files: moq, dev, prod

.env.dev

VUE_APP_ENV=dev
VUE_APP_HEADER_COLOR=#9F1F36

.env.moq

VUE_APP_ENV=moq
VUE_APP_HEADER_COLOR=#FFD200

.env.prod

VUE_APP_ENV=prod
VUE_APP_HEADER_COLOR=

as you can see I also like to add a a color code so that i can easily tell which environment I am currently working on.

next I create a services folder where I keep all my logic that communicates outside my app and a mock_data folder where I keep json files that will represent the responses that I would get from my backend api.


so let's create a simple json file for our mock data, here i have created a openings.json file that contains three chess openings

[
  {
    "description""Sacrifice a pawn for a head start",
    "id""55d86e6f-b043-4544-ae6b-b88ee4ee134b",
    "name""King's gambit",
    "tags": ["kingsPawn""gambit"]
  },
  {
    "description""trade a pawn for central occupation",
    "id""52366eb7-fcad-4b58-9ad3-f73c1585c16a",
    "name""Queens's gambit",
    "tags": ["queensPawn""gambit"]
  },
  {
    "description""Win by oversight",
    "id""e5f7f895-073c-4a24-9df0-d9d65b6ad46a",
    "name""Scholar's mate",
    "tags": ["kingsPawn""dubious"]
  }
]

this would be the payload i would expect from some web api get openings, but for now we will just use local mock data.

**in your tsconfig.json and make sure to add "resolveJsonModule": true to the compilerOptions

next create a openingsService.ts file in which you should define an interface and two services a private mock service and a default prod/dev service. however rather than creating a context and pass that into the service service what i do is use the object.assign function to override my dev/prod services with a mock counter part.

import { IOpening } from "@/models/opening";

export interface IOpeningService {
  getOpeningsAsync: () => Promise<IOpening[]>
}

class MoqOpeningService implements IOpeningService {
  getOpeningsAsync: () => Promise<IOpening[]> = 
    async () => {
      return require<IOpening[]>("@/mock_data/openings.json");
    };
}

export default class OpeningService implements IOpeningService {
  constructor() {
    if(process.env.VUE_APP_ENV === "moq")
      Object.assign(thisnew MoqOpeningService());
  }

  getOpeningsAsync: () => Promise<IOpening[]> = 
    async () => {
      throw new Error("getOpeningsAsync not implemented");
    }
}

notice that in the constructor of the default class there is a check of which environment is running and if it is the "moq" version a new instance of the MoqOpeningService is assigned to the current opening service overriding everything defined in the interface. 

now that is fine and dandy however we have one final caveat, all of your mock data is going to be packaged in your dist folder when you build your project.

To exclude mock files from your build 

firstly you have to add a reference to the webpack npm package 

i used the 4.45.0 version because I was getting exceptions with the latest major 5+ version

npm install webpack@^4.45.0 --save-dev

with webpack install if you haven't already created a vue.config.js file go a head and do that at the root of your project, refer to the project structure screen capture at the start of this post.

//vue.config.js

// eslint-disable-next-line @typescript-eslint/no-var-requires
const webpack = require('webpack')

module.exports = {  
  configureWebpack: {
    plugins: process.env.VUE_APP_ENV == "prod" ? [
      new webpack.IgnorePlugin({
        resourceRegExp: /mock_data\/.*\.json$/,
        contextRegExp: /services$/
      })
    ] : []
  }
}

IgnorePlugin | webpack

now when you build your project your mock data will not be included in your dist folder.


Monday, 24 January 2022

Vue.js 3 include webconfig in build

So you pushed your SPA build with Vue.js to Azure and if you refresh anything deeper than your index page you are getting a "resource not available" or something along those lines. 

well my dear friend you need to include a webconfig file in your build, now the problem is that if you include a webconfig in your project that file is ignored in your build. 

before we tackle that problem, let's start with the actual web config.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
    <rewrite>
      <rules>
        <rule name="Main Rule" stopProcessing="true">
                <match url=".*" />
                <conditions logicalGrouping="MatchAll">
                    <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                </conditions>
                <action type="Rewrite" url="/" />
            </rule>
        </rules>
    </rewrite>
</system.webServer>
</configuration>

Create a file in your project somewhere and call it web.config

Now let's focus on copying it over to your build result.

to pull this off you will need to install the copy webpack plugin

    "copy-webpack-plugin": "^6.4.1",

ensure that it's version 6.x or at least at the time of this blog post

npm install copy-webpack-plugin@6 --save-dev

next open your vue.config.js file and use your webpack copy plugin to copy your webconfig to your dist folder.

// eslint-disable-next-line @typescript-eslint/no-var-requires
const webpack = require('webpack');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const CopyPlugin = require('copy-webpack-plugin');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const path = require('path');

module.exports = {  
  chainWebpack: config => {
    config
    .plugin('html')
    .tap(args => {
      args[0].title = 'My app name'
      return args
    })
   
   
  },
  configureWebpack: {
    plugins: [
      new CopyPlugin({
        patterns: [
          {
            from: path.resolve(__dirname, "src", "web.config"),
            to: path.resolve(__dirname, "dist",),
          }
        ]
      })
    ]
  }
}

and voila, npm run build and you should see your web.config file in your dist folder.



Thursday, 16 September 2021

Opportunity: Initial problem

When I write about opportunity, what i mean is the initial problem definition, why are we here and what are we trying to solve? there are three main components to the opportunity stage


Identifying the opportunity for improvement is the first challenge of a design team, problems can come from many directions and usually are not as clear cut as they first seem; Often times symptoms masquerade as problems, for example I once worked with a client where it took 48 hours from the moment a user requested access to a system before it was either granted or the user was informed that they needed to take the appropriate training; after some investigation, the problem seemed to be a 48 hour service level agreement (SLA) with a 3rd party provider who was paid per ticket.

The provider was not willing to budge on the 48 hour SLA putting us at an impasse, but once we asked why is this handled by a service provider we started to gain some traction. We ended up cutting down a 48 hour turnaround time into a 10 second one by circumventing the service provider with a chatbot.

Now the above is a very simple example that nicely illustrates that the symptoms of a "slow" system  was actually the result of of an underlying problem of not having a dedicated admin that could handle the request, and that in fact this "admin" wasn't even necessary.

When we are trying to define our problem we should pursue questions along the following lines:
  • What is the actual problem underneath the symptom(s)?
    • is there a resolution?
      • what are the components of the resolution 
      • can the components be modified or reordered
  • Who is affected by the problem, 
    • our staff
    • our clients
    • our management
    • some of the above?
    • all of the above?
  • who has tried to solve this problem? 
    • Other teams? 
    • our competition? 
    • has anyone solved it 
    • has anyone moved towards a solution?
  • When did the problem start being a problem? 
    • this year?
    • this quarter? 
    • last week? 
    • why is it important now?
      • has it always been there?
      • has something changed that suddenly it's more serious? or just more visible?
        • has management changed
        • was someone managing the problem and no longer is
          • if yes, how? 
          • if no, why?
  • Has someone failed to solve this problem in the past
    • what can we learn from their failures
    • what did they do wrong
      • did they understand what the root cause was
  • where is this problem
    • local?
    • regional?
    • national?
    • global?
    • is it a cultural specific challenge?
  • Source of the problem
    • Internal
      • management
      • union
      • personnel
    • Partner
      • 3rd party provider
      • strategic alliance 
    • Competitor
      • Direct
      • Indirect
      • Disruptive tech
    • Government
      • Law
    • Cultural change
  • Why is it worth solving
    • enough impact to dedicate time and resources
    • will the solution be cheaper then the problem
      • in the long run
      • immediately 
      • does it matter (if it's legislation driven)
The most important thing is to dive deep, when investigation our problem, not to stop after the first level of questioning but to keep chipping away until we can get to the root cause, it's a very contextual approach, it's hard to say when you've gone deep enough. For my chat bot solution, the line of questioning that got us to our solution went something like this:
  1. Why does it take more than two days for new users to get anything done in this system?
    Because it takes up to 48 hours to gain access
  2. Why does it take 48 hours to gain access
    because it's a 3rd party provider that handles it for us on a ticket basis
  3. why do we use a 3rd party provider
    because its a rarely accessed system and it doesn't make sense to have a dedicated admin
  4. Is the system ours? or the 3rd parties?
    it's our internal system
  5. is there a rest API that we could use to access the system
    Yes.
The above is a very neat and tidy representation of what we did, in reality there where many dead ends we investigated many points of contact, many frustrations and many experts we had to consult, which brings me to my next point, just because our team has a technical component it doesn't mean it's the right technical component, when solving problems as a team you have to move beyond yourselves and consider solutions that you may not be well versed in. in those case you need ask yourselves:
  1. What expertise do we need?
  2. Who has the expertise that we need?
    • how much does it cost?
    • do we need external help?
One of the biggest challenges that we have to overcome is getting a handle on what the root cause of our challenge is, to make matters worse often times the client doesn't truly understand what's wrong:
  • The wrong problem being presented by the client
  • The wrong solution being presented by the client
in both cases it's easy to follow the wrong path, if the client pays you to solve the wrong problem or even worse they give you the solution to implement you are left in the worst possible situation where even if you are successful you still fail. If you solve the wrong problem 100% effectively you are still 100% wrong.