Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggested additions to the repo #1

Open
wants to merge 54 commits into
base: review
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
ee99648
Suggested additions to the repo
domsinclair Feb 23, 2024
bda1bab
Update Markdown/CreatingAspects/cr2.md
domsinclair Feb 24, 2024
c7006c0
initial deposit
domsinclair Feb 26, 2024
1ed8d2a
Merge branch 'master' of https://github.com/postsharp/postsharp-emails
domsinclair Feb 26, 2024
7d68406
resolving branches
domsinclair Feb 26, 2024
0a2e37d
GPT-based corrections
gfraiteur Feb 27, 2024
27ba6ae
Linearizing articles.
gfraiteur Feb 27, 2024
540c4c8
Taking the review into account.
gfraiteur Feb 27, 2024
43cc1a7
Merge branch 'gpt-corrections' into linearize
gfraiteur Feb 27, 2024
9f16f46
More work on linearized articles
gfraiteur Feb 27, 2024
7cc6405
Merge pull request #2 from postsharp/gpt-corrections
gfraiteur Feb 27, 2024
35fdbf0
Discussion with dom.
gfraiteur Feb 28, 2024
b8d8e06
Additions to original emails
domsinclair Mar 15, 2024
33fee89
Merge branch 'linearize'
gfraiteur Apr 9, 2024
c817926
Merge remote-tracking branch 'origin/Temporary'
gfraiteur Apr 9, 2024
88a702a
GPT spell check.
gfraiteur Apr 10, 2024
c8be315
Editing articles 10 to 51.
gfraiteur Apr 10, 2024
eb0ead3
Create CNAME
gfraiteur Apr 10, 2024
4d5100a
Delete CNAME
gfraiteur Apr 10, 2024
c8bc9b4
Create CNAME
gfraiteur Apr 10, 2024
17d1516
Delete CNAME
gfraiteur Apr 10, 2024
f6d9a58
Create CNAME
gfraiteur Apr 10, 2024
0a00436
Moving files.
gfraiteur Apr 10, 2024
79739bd
Merge branch 'master' of https://github.com/postsharp/postsharp-emails
gfraiteur Apr 10, 2024
a4ef649
Renaming.
gfraiteur Apr 10, 2024
ccf4f5d
Setting theme.
gfraiteur Apr 10, 2024
9cceef9
Delete CNAME
gfraiteur Apr 10, 2024
513d788
Create static.yml
gfraiteur Apr 10, 2024
d99137e
Deleting config.yml
gfraiteur Apr 10, 2024
90c7407
Resizing images.
gfraiteur Apr 10, 2024
a08ffe4
Merge branch 'master' of https://github.com/postsharp/postsharp-emails
gfraiteur Apr 10, 2024
8338be0
Completed the reference validation article.
gfraiteur Apr 10, 2024
76aa23e
Spellcheck.
gfraiteur Apr 10, 2024
1daeabc
Update README.
gfraiteur Apr 10, 2024
fec01b1
Update README.
gfraiteur Apr 10, 2024
dbf653b
Merge branch 'master' of https://github.com/postsharp/postsharp-emails
gfraiteur Apr 11, 2024
b0879e6
Logging Methods, Including Parameter Values
gfraiteur Apr 11, 2024
043f638
Spellcheck
gfraiteur Apr 11, 2024
75d7a72
Removed conflicts with Liquid.
gfraiteur Apr 12, 2024
c577e8c
Multi-targeting email
gfraiteur Apr 23, 2024
4f89dbd
INotifyPropertyChanged on Your Terms
gfraiteur Apr 23, 2024
373a246
Aspect inheritance.
gfraiteur Apr 23, 2024
e9920e5
Architecture validation.
gfraiteur Apr 23, 2024
78b35ed
Logging with DI
gfraiteur Apr 23, 2024
cca4516
Eligibility.
gfraiteur Apr 23, 2024
d630c93
Eligigiblity article - remark
gfraiteur Apr 23, 2024
66e91a0
Adding awareness email.
gfraiteur Jul 26, 2024
2fee58b
Caching
gfraiteur Jul 26, 2024
6f69f3b
Edits.
gfraiteur Jul 26, 2024
603ea68
Awareness email.
gfraiteur Oct 2, 2024
8b4cc3f
Updated examples.
gfraiteur Oct 4, 2024
60fb66a
Awareness emails: last.
gfraiteur Oct 14, 2024
92859d5
Goat review: architecture.
gfraiteur Oct 14, 2024
26255e5
Edits.
gfraiteur Oct 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions Markdown/CommonTasks/ct1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Common Tasks: Validating String Length

Inevitably almost every developer will be faced with the necessity to validate the length of a string prior to doing something with it, an obvious case in point would be specifying criteria for a password.

At this point it's not uncommon for new or relatively inexperienced developers to create a simple checking method to satisfy the immediate requirement.

```c#
bool ValidateString(string input)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be clearer to throw an exception since this is what the aspect is doing.

{
return input.length < 10 && input.length > 16;
}
```

If the requirement was that the string be no less than 10 characters in length and no more than 16 then undeniably it will produce a result. If at some point further on in the program another similar check is required then it's very easy to see how this might be copied and pasted with a a couple of alterations to match the next requirement.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph is not clear especially "it will produce a result" but having a more realistic example above would help.


Using Metalama this task can be easily solved, as it has a number of pre-made contracts for just this type of scenario.

You could write code as follows (using a simple console application as an example);

```c#
using Metalama.Patterns.Contracts;

namespace CommonTasks
{
internal class Program
{

[StringLength(10, 16)]
private static string? password;

static void Main(string[] args)
{
try
{
Console.WriteLine("Enter your Password: ");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I think the example above (the first one) should align with this code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And that of course was exactly where I got distracted and made the pull request to the contracts!

password = Console.ReadLine();
Console.WriteLine("Your password meets the basic length requirement.");
} catch(ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
```

Metalama's StringLength aspect takes as parameters either a maximum, or a minimum and maximum length and in the event of a validation failure throws a System.ArgumentException.

At the point of compilation it adds the necessary logic into your code.

```c#
using Metalama.Patterns.Contracts;

namespace CommonTasks
{
internal class Program
{


private static string? _password1;


[StringLength(10, 16)]
private static string? password
{
get
{
return Program._password1;


}
set
{
if (value != null && (value!.Length < 10 || value.Length > 16))
{
throw new ArgumentException($"The 'password' property must be a string with length between {10} and {16}.", "value");
}
Program._password1 = value;


}
}
static void Main(string[] args)
{
try
{
Console.WriteLine("Enter your Password: ");
password = Console.ReadLine();
Console.WriteLine("Your password meets the basic length requirement.");
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
```

The benefit of using Metalama to handle such tasks is that they are handled consistently whilst your own code remains concise, easy to read and comprehend.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good at this point to say that the same aspect can be used on parameters, properties or return values, with one more example when set on a parameter.

Copy link
Collaborator Author

@domsinclair domsinclair Feb 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok the rewritten ct1 is here. Apologies as my lack of git skills coming to the fore again!!

Common Tasks: Input / Output Validation

Many developers will be familiar with the phrase 'Garbage in, garbage out'. In essence if the input being entered into an application is rubbish one shouldn't be surprised if what comes out is rubbish as well. To avoid this developers' need to ensure that what goes into their application's routines meet acceptable criteria and equally what comes out does the same.

Validation is a task that every developer will face at some point and the approach that they take is more often than not a good indication of their overall development experience.

As an example consider a basic requirement that a given string must fall within a given number of characters.

A new or relatively inexperienced developers might create a simple checking method to satisfy the immediate requirement.


bool ValidateString(string input)
{
    return input.length < 10 && input.length > 16;
}

If the requirement was that the string be no less than 10 characters in length and no more than 16 then this very simple validation will at least provide a answer to the basic question 'Does this string fall within the defined character length?' However it doesn't really handle failure. Over time developers' will learn how to approach this properly but they will still find themselves having to take differing approaches depending on whether they are validating parameter inputs, properties or results.

Using Metalama tasks like this can be solved easily. It has a patterns library that itself has a number of pre-made contracts for a wide range of scenarios including the example under discussion.

You could write code as follows (using a simple console application as an example) employing no more than a simple attribute;


using Metalama.Patterns.Contracts;

namespace CommonTasks
{
    internal class Program
    {

        [StringLength(10, 16)]
        private static string? password;

        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Enter your Password:  ");
                password = Console.ReadLine();
                Console.WriteLine("Your password meets the basic length requirement.");
            } catch(ArgumentException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Metalama's StringLength aspect takes as parameters either a maximum, or a minimum and maximum length and in the event of a validation failure throws a System.ArgumentException.

At the point of compilation it adds the necessary logic into your code.


using Metalama.Patterns.Contracts;

namespace CommonTasks
{
    internal class Program
    {


        private static string? _password1;


        [StringLength(10, 16)]
        private static string? password
        {
            get
            {
                return Program._password1;


            }
            set
            {
                if (value != null && (value!.Length < 10 || value.Length > 16))
                {
                    throw new ArgumentException($"The  'password' property must be a string with length between {10} and {16}.", "value");
                }
                Program._password1 = value;


            }
        }
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Enter your Password:  ");
                password = Console.ReadLine();
                Console.WriteLine("Your password meets the basic length requirement.");
            }
            catch (ArgumentException ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

There are numerous benefits to using Metalama contracts for validation. They are named in such a way that their intention is clear and where appropriate accept parameters that provide flexibility in the rules being tested. When validation fails it does so by throwing standard exceptions that are easy to handle. The real benefit though is that they can be used in exactly the same way to validate both inputs and outputs.

In the two examples that follow the task remains the same but instead of validating a property input parameters are validated in the first example , and the actual output in the second. In both cases the code that Metalama adds at compilation is also shown.


static string CreatePasswordValidatingInputs([StringLength(5,8)]string a, [StringLength(5, 8)] string b)
{
    return  a + b;
}

Which at compile time becomes;


     static string CreatePasswordValidatingInputs([StringLength(5,8)]string a, [StringLength(5, 8)] string b)
     {
         if (b.Length < 5 || b.Length > 8)
         {
             throw new ArgumentException($"The  'b' parameter must be a string with length between {5} and {8}.", "b");
         }
         if (a.Length < 5 || a.Length > 8)
         {
             throw new ArgumentException($"The  'a' parameter must be a string with length between {5} and {8}.", "a");
         }
         return a + b;

     }

And for outputs;


 [return: StringLength(10,16)]
 static string CreatePasswordValidatingOutput(string a, string b)
 {
     return a + b;
 }

Which at compile time becomes;

    [return: StringLength(10,16)]
   static string CreatePasswordValidatingOutput(string a, string b)
   {
       string returnValue;
       returnValue = a + b;


       if (returnValue.Length < 10 || returnValue.Length > 16)
       {
           throw new PostconditionViolationException($"The  return value must be a string with length between {10} and {16}.");
       }
       return returnValue;

   }

Exactly the same contract being used in ostensibly the same way via an attribute for three quite different validation scenarios but producing consistent code at compile time that the developer has not had to write by hand.

Whilst it should be pointed out that there is a StringLength attribute that forms part of the system.ComponentModel.DataAnnotations library it does not offer the same versatility oas that provide by Metalama.Patterns.Contracts in as much as it cannot be applied to return values and there is a necessity for the developer to provide their own error message.


If you'd like to know more about Metalama in general then visit our website.

You can learn more about Metalama contracts here.

Why not join us on Slack where you can keep up with what's new and get answers to any technical questions that you might have.


<br>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also say how it differs from system DataAnnotations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered in revision added to earlier comment.


If you'd like to know more about Metalama in general then visit our [website](https://www.postsharp.net/metalama).

You can learn more about Metalama contracts [here](https://doc.postsharp.net/metalama/patterns/contracts).

Why not join us on [Slack](https://www.postsharp.net/slack) where you can keep up with what's new and get answers to any technical questions that you might have.
103 changes: 103 additions & 0 deletions Markdown/CommonTasks/ct2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Common Tasks: Meeting Requirements
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Common Tasks: Meeting Requirements
# Common Tasks: Verifying required parameters and fields

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense.


It's not uncommon for developers to have to ensure that certain key properties or return values actually have a value. Although the code required to perform the checks needed to ensure that a value is present are not difficult to write they do have the effect of of making your code look cluttered.

A typical string property might end up looking like this.

```c#
public class ApplicationUser
{
private string userName;

public string UserName
{
get { return userName; }
set
{
if (!string.IsNullOrWhiteSpace(value))
{
userName = value;
}
else
{
throw new ArgumentException("Invalid value for MyString. Value must not be null or blank.");
}
}
}
}
```

Metalama can make this task much simpler. Using exactly the same string property as an example you would just require the following.

```c#
using Metalama.Patterns.Contracts;

namespace CommonTasks.Required
{
public class ApplicationUser
{
[Required]
public string UserName { get; set; }

}
}
```

Not only is the code cleaner but it is immediately apparent to anyone reading it that the UserName property is actively required in the overall working of the application. That is something that cannot be inferred as quickly by looking at the first example.

At compile time Metalama will add all the code that is necessary to ensure that the UserName property must be present.

```c#
using Metalama.Patterns.Contracts;

namespace CommonTasks.Required
{
public class ApplicationUser
{
private string _userName = default!;

[Required]
public string UserName
{
get
{
return this._userName;


}
set
{
if (string.IsNullOrWhiteSpace(value))
{
if (value == null!)
{
throw new ArgumentNullException("value", "The 'UserName' property is required.");
}
else
{
throw new ArgumentOutOfRangeException("value", "The 'UserName' property is required.");
}
}
this._userName = value;


}
}
}
}

```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good here to talk about [NotNull] and about nullable reference types in C# and how we can use a fabric method. See https://doc.postsharp.net/metalama/patterns/contracts/enforcing-non-nullability.

Metalama has actually reinforced the check on our behalf ensuring that there is a clear distinction between a null value being passed to the property and a simple space making it easier to diagnose errors.

Metalama has a wide range of pre-built contracts that you can use in situations like this where it is necessary to ensure that fields, properties, parameters or return values meet certain conditions. In every case all you need to do is add the relevant attribute to you code in the correct place and Metalama will add the necessary additional code at compile time. Examples include phone, email and credit card number checks to name but three.

Doing this manually is time consuming and it can be prone to error. Metalama removes the chore of writing repetitive code, makes your intention clearer to anyone else who reads your code at a later date and leaves you safe in the knowledge that it will just work as it should when required.

<br>

If you'd like to know more about Metalama in general then visit our [website](https://www.postsharp.net/metalama).

You can learn more about Metalama contracts [here](https://doc.postsharp.net/metalama/patterns/contracts).

Why not join us on [Slack](https://www.postsharp.net/slack) where you can keep up with what's new and get answers to any technical questions that you might have.
80 changes: 80 additions & 0 deletions Markdown/CommonTasks/ct3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Common Tasks: INotifyPropertyChanged

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would postpone this article until we have a stable ready-made implemetation of INotifyPropertyChanged.

We have come to expect application user interfaces to respond almost instantaneously to data that we input into them. This has been made possible by UI's that are built around data bound controls in architecture that implements patterns such as MVVM (Model, View, ViewModel).

In simple language this works because the UI is able to update when properties in the underlying data models change and in so doing raise the PropertyChanged event. This logic is encapsulated in the INotifyPropertyChanged interface. This pattern has been widely adopted because one of the major benefits that it provides is the ability to reuse data models with different Different views.

There is however one very notable drawback to using this interface. It requires a great deal of repetitive boiler plate code and that code is not produced automatically so it's possible to omit parts of it unintentionally.

Dot Net already has an INotifyPropertyChanged Interface so why not just use that. The drawback to that approach is illustrated below.

<br>

![](images/ct1.gif)

<br>

The standard Visual Studio intellisense for this barely does anything. There is still a need to adjust the properties so that they actually raise the event and the event itself needs to be handled.

If Metalama is used to implement INotifyPropertyChanged then all of the additional code required to make this work will be taken care of. It will be necessary to create an aspect to do this but fortunately there is a great example of such an aspect in the [Metalama Documentation](https://doc.postsharp.net/metalama/examples/notifypropertychanged).

```c#
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using System.ComponentModel;

namespace CommonTasks.NotifyPropertyChanged
{
[Inheritable]
internal class NotifyPropertyChangedAttribute : TypeAspect
{
public override void BuildAspect(IAspectBuilder<INamedType> builder)
{
builder.Advice.ImplementInterface(builder.Target, typeof(INotifyPropertyChanged), OverrideStrategy.Ignore);

foreach (var property in builder.Target.Properties.Where(p =>
!p.IsAbstract && p.Writeability == Writeability.All))
{
builder.Advice.OverrideAccessors(property, null, nameof(this.OverridePropertySetter));
}
}

[InterfaceMember]
public event PropertyChangedEventHandler? PropertyChanged;

[Introduce(WhenExists = OverrideStrategy.Ignore)]
protected void OnPropertyChanged(string name) =>
this.PropertyChanged?.Invoke(meta.This, new PropertyChangedEventArgs(name));

[Template]
private dynamic OverridePropertySetter(dynamic value)
{
if (value != meta.Target.Property.Value)
{
meta.Proceed();
this.OnPropertyChanged(meta.Target.Property.Name);
}

return value;
}
}
}
```

<br>

If you read through the code you should be able to see that it implements the INotifyPropertyChanged interface. Having done that it loops through the properties amending their setters where required to raise the the property changed event and finally it correctly implements the INotifyPropertyChanged interface. With the aspect added to your project The INotifyPropertyChanged implementation is made much simpler.

<br>

![](images/ct2.gif)

<br>

In what was an admittedly small and contrived sample class Metalama successfully implemented the INotifyPropertyChanged interface and in the process saved us having to add 50 additional lines of code. Over the entirety of a larger real world example the savings in writing repetitive boiler plate code will be considerable.

<br>

If you'd like to know more about Metalama in general then visit our [website](https://www.postsharp.net/metalama).

Why not join us on [Slack](https://www.postsharp.net/slack) where you can keep up with what's new and get answers to any technical questions that you might have.
79 changes: 79 additions & 0 deletions Markdown/CommonTasks/ct4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Common Tasks: Validating Code (Naming Conventions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Common Tasks: Validating Code (Naming Conventions)
# Common Tasks: Validating Naming Conventions


One of the most difficult things to validate particularly for large teams but it can be equally applicable to smaller teams or even individuals working on an a large codebase are Naming conventions.

Properly named methods can frequently convey intent or purpose just from their name so having rules in place to enforce this is not uncommon. The issue tends to be with how those rules are then enforced.-

By way of an example imagine a situation where an application makes extensive use of stream readers and there are several classes created by different members of the team that implement said readers to perform various tasks. A decision has been taken to ensure that all such classes have the suffix 'StreamReader' added to their names so that it is clear what they do.

Fabrics are a great way to enforce this type of validation, particularly ProjectFabric as they can cover an entire project.

We'll create a Fabric that checks the codebase to ensure that the naming convention is being adhered to by the developers.

<br>

```c#
using Metalama.Extensions.Architecture.Fabrics;
using Metalama.Framework.Fabrics;


internal class NamingConvention : ProjectFabric
{


public override void AmendProject(IProjectAmender amender)
{ amender.Verify().SelectTypesDerivedFrom(typeof(StreamReader)).MustRespectNamingConvention("*Reader"); }


}
```

<br>

In the code above the fabric looks at each class in the project that is derived from `StreamReader`. If the name of any class that matches that criteria does not end in'Reader' then a warning is displayed.

With our custom validation rule written let's put it to the test. In the code below we have two classes derived from StreamReader. One has the reader suffix the other does not and as such it should cause a warning to be displayed.

<br>

```c#
namespace CommonTasks.NamingConventions
{
internal class FancyStream : StreamReader
{


public FancyStream(Stream stream) : base(stream)
{
}


}


internal class SuperFancyStreamReader : StreamReader
{
public SuperFancyStreamReader(Stream stream) : base(stream)
{
}
}
}
```

<br>

We can see our warning in action below.

<br>

![](images/ct3.gif)

<br>

This is clearly a very simple example but it does illustrate how Metalama can be used to help validate your codebase and enforce rules. More information about this can be found in the [Metalama Documentation](https://doc.postsharp.net/metalama/conceptual/architecture/naming-conventions).

<br>

If you'd like to know more about Metalama in general then visit our [website](https://www.postsharp.net/metalama).

Why not join us on [Slack](https://www.postsharp.net/slack) where you can keep up with what's new and get answers to any technical questions that you might have.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would also need an article about validating references.

1 change: 1 addition & 0 deletions Markdown/CommonTasks/ct5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Common Tasks:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean something here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's for me to know and you to guess!!

Simply a placeholder for a new email that's all.

Binary file added Markdown/CommonTasks/images/ct1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Markdown/CommonTasks/images/ct2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Markdown/CommonTasks/images/ct3.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading