-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: review
Are you sure you want to change the base?
Changes from 1 commit
ee99648
bda1bab
c7006c0
1ed8d2a
7d68406
0a2e37d
27ba6ae
540c4c8
43cc1a7
9f16f46
7cc6405
35fdbf0
b8d8e06
33fee89
c817926
88a702a
c8be315
eb0ead3
4d5100a
c8bc9b4
17d1516
f6d9a58
0a00436
79739bd
a4ef649
ccf4f5d
9cceef9
513d788
d99137e
90c7407
a08ffe4
8338be0
76aa23e
1daeabc
fec01b1
dbf653b
b0879e6
043f638
75d7a72
c577e8c
4f89dbd
373a246
e9920e5
78b35ed
cca4516
d630c93
66e91a0
2fee58b
6f69f3b
603ea68
8b4cc3f
60fb66a
92859d5
26255e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
{ | ||
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: "); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ValidationMany 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.
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also say how it differs from system DataAnnotations. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,103 @@ | ||||||
# Common Tasks: Meeting Requirements | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||||||
|
||||||
|
||||||
} | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
``` | ||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good here to talk about |
||||||
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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# Common Tasks: INotifyPropertyChanged | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,79 @@ | ||||||
# Common Tasks: Validating Code (Naming Conventions) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We would also need an article about validating references. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Common Tasks: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean something here? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
There was a problem hiding this comment.
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.