Jekyll2022-04-30T13:14:36+00:00https://jeremyskinner.co.uk/feed.xmlJeremy Skinnerusing System.DraggyDroppy;FluentValidation 10.0 Released2021-04-06T00:00:00+00:002021-04-06T00:00:00+00:00https://jeremyskinner.co.uk/2021/04/06/fluentvalidation-10-released<p>FluentValidation 10.0 is <a href="https://nuget.org/packages/fluentvalidation">now available</a>. This is a major release that has been under development for the last year and contains various performance improvements.</p>
<p>Please ensure you read the <a href="https://docs.fluentvalidation.net/en/latest/upgrading-to-10.html">upgrade guide</a> before upgrading from FluentValidation 9.x to 10.0.</p>
<p>The goal of this release was to improve performance and memory usage, as well as improve some longstanding issues with the library’s internal API. To accomodate these changes and improvements, we had to make some breaking changes in a few areas. Users of the public API (the fluent interface) shouldn’t be affected, but if you make use of our Internal API, or you write custom validators then you’ll need to update your code (see the <a href="https://docs.fluentvalidation.net/en/latest/upgrading-to-10.html">upgrade guide</a>).</p>
<p>Full release notes an be <a href="https://github.com/FluentValidation/FluentValidation/releases">found on the releases page</a>.</p>
<p>From a performance standpoint, memory usage has almost halved and validation speed is over 1.5x faster for a large validation run. The following benchmarks were run on a Lenovo Thinkpad t480s laptop running .NET Core 3.1 on Windows 10 Pro with an Intel Core i7-8550U and 16gb RAM:</p>
<ul>
<li>FluentValidation 9.2.0: 748.24 MB, 1491.44 ms</li>
<li>FluentValidation 10.0.0: 440.21 MB, 535.84 ms</li>
</ul>
<p>Additional benchmark code can be found <a href="https://github.com/FluentValidation/FluentValidation/blob/main/src/FluentValidation.Tests.Benchmarks/ValidationBenchmark.cs">in the repository</a>.</p>FluentValidation 10.0 is now available. This is a major release that has been under development for the last year and contains various performance improvements.FluentValidation 9.2 and 9.3-preview1 released2020-08-26T00:00:00+00:002020-08-26T00:00:00+00:00https://jeremyskinner.co.uk/2020/08/26/fluentvalidation-92-released<p>FluentValidation 9.1 is <a href="https://nuget.org/packages/fluentvalidation">now available</a>. This release contains new features and performance improvements.</p>
<h2 id="inheritance-validator">Inheritance Validator</h2>
<p>FluentValidation 9.2 contains the new Inheritance Validator, which allows you to set up specific child validators for different subclasses/implementors depending on their runtime type.</p>
<p>For example, imaging the following example:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// We have an interface that represents a 'contact',</span>
<span class="c1">// for example in a CRM system. All contacts must have a name and email.</span>
<span class="k">public</span> <span class="k">interface</span> <span class="nc">IContact</span> <span class="p">{</span>
<span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">string</span> <span class="n">Email</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="c1">// A Person is a type of contact, with a name and a DOB.</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Person</span> <span class="p">:</span> <span class="n">IContact</span> <span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Email</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">DateTime</span> <span class="n">DateOfBirth</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="c1">// An organisation is another type of contact,</span>
<span class="c1">// with a name and the address of their HQ.</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">Organisation</span> <span class="p">:</span> <span class="n">IContact</span> <span class="p">{</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Name</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Email</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="n">Address</span> <span class="n">Headquarters</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Our model class that we'll be validating.</span>
<span class="c1">// This might be a request to send a message to a contact.</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ContactRequest</span> <span class="p">{</span>
<span class="k">public</span> <span class="n">IContact</span> <span class="n">Contact</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">MessageToSend</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Next we create validators for Person and Organisation:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PersonValidator</span> <span class="p">:</span> <span class="n">AbstractValidator</span><span class="p"><</span><span class="n">Person</span><span class="p">></span> <span class="p">{</span>
<span class="k">public</span> <span class="nf">PersonValidator</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Name</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Email</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">DateOfBirth</span><span class="p">).</span><span class="nf">GreaterThan</span><span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="n">MinValue</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">OrganisationValidator</span> <span class="p">:</span> <span class="n">AbstractValidator</span><span class="p"><</span><span class="n">Organisation</span><span class="p">></span> <span class="p">{</span>
<span class="k">public</span> <span class="nf">OrganisationValidator</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Name</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Email</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">HeadQuarters</span><span class="p">).</span><span class="nf">SetValidator</span><span class="p">(</span><span class="k">new</span> <span class="nf">AddressValidator</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now we create a validator for our <code class="language-plaintext highlighter-rouge">ContactRequest</code>. We can define specific validators for the <code class="language-plaintext highlighter-rouge">Contact</code> property, depending on its runtime type. This is done by calling <code class="language-plaintext highlighter-rouge">SetInheritanceValidator</code>, passing in a function that can be used to define specific child validators:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">ContactRequestValidator</span> <span class="p">:</span> <span class="n">AbstractValidator</span><span class="p"><</span><span class="n">ContactRequest</span><span class="p">></span> <span class="p">{</span>
<span class="k">public</span> <span class="nf">ContactRequestValidator</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Contact</span><span class="p">).</span><span class="nf">SetInheritanceValidator</span><span class="p">(</span><span class="n">v</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">v</span><span class="p">.</span><span class="n">Add</span><span class="p"><</span><span class="n">Organisation</span><span class="p">>(</span><span class="k">new</span> <span class="nf">OrganisationValidator</span><span class="p">());</span>
<span class="n">v</span><span class="p">.</span><span class="n">Add</span><span class="p"><</span><span class="n">Person</span><span class="p">>(</span><span class="k">new</span> <span class="nf">PersonValidator</span><span class="p">());</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="performance-improvements">Performance Improvements</h2>
<p>FluentValidation 9.2 also contains several performance improvements which should make a difference if you’re invoking <code class="language-plaintext highlighter-rouge">Validate</code> on a large number of objects.</p>
<h2 id="fluentvalidation-93-preview1-and-net-5-support">FluentValidation 9.3-Preview1 and .NET 5 support</h2>
<p>FluentValidation 9.3-preview1 has also been released at the same time, which adds support for .NET 5 Preview 8.</p>FluentValidation 9.1 is now available. This release contains new features and performance improvements.FluentValidation 9.1 released2020-08-08T00:00:00+00:002020-08-08T00:00:00+00:00https://jeremyskinner.co.uk/2020/08/08/fluentvalidation-91-released<p>FluentValidation 9.1 is <a href="https://nuget.org/packages/fluentvalidation">now available</a>. This release contains a few new features and some bug fixes. <a href="https://github.com/FluentValidation/FluentValidation/releases/tag/9.1.0">View the full release notes</a>. A few highlights:</p>
<h3 id="simplifying-overloads-of-validate">Simplifying overloads of Validate</h3>
<p>FluentValidation has several overloads of its <code class="language-plaintext highlighter-rouge">Validate</code> method which have caused some confusion. For example, the overload that accepted a <code class="language-plaintext highlighter-rouge">params</code> array of property names has been in place since very early releases,
which allowed for validating only specific properties with <code class="language-plaintext highlighter-rouge">validator.Validate(instance, "Forename", "Surname")</code></p>
<p>However, this called ambiguity when we later introduced <a href="https://docs.fluentvalidation.net/en/latest/rulesets.html">rule sets</a> back in version 3.0 as you had to use a named parameter so as not to conflict with the property-name overload above:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">ruleSet</span><span class="p">:</span> <span class="s">"MyRuleSet"</span><span class="p">);</span>
</code></pre></div></div>
<p>If you forgot the explicit named parameter, the property names overload would be invoked instead. This became more confusing when we enabled validation of multiple rulsets, as you had to specify these
with a comma-separated string:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">ruleSet</span><span class="p">:</span> <span class="s">"MyRuleSet,SomeOtherRuleSet"</span><span class="p">);</span>
</code></pre></div></div>
<p>On top of this confusion, there are various other overloads that allow specifying a validator selector, property names as expressions, and whether the validator should throw an exception or not, on top of the “core” validate method that did all the work (which takes a raw <code class="language-plaintext highlighter-rouge">ValidatioCntext</code>).</p>
<p>To try and tidy this up and avoid confusion, most of these overloads have now been deprecated and will be removed in FluentValidation 10. Instead, you can explicitly opt in to which features you want to use by passing in a set of configuration options:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Validate specific rulesets</span>
<span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">opt</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">opt</span><span class="p">.</span><span class="nf">IncludeRuleSets</span><span class="p">(</span><span class="s">"MyRuleSet"</span><span class="p">,</span> <span class="s">"SomeOtherRuleSet"</span><span class="p">);</span>
<span class="c1">// Can also force rules not in a ruleset to be run</span>
<span class="n">opt</span><span class="p">.</span><span class="nf">IncludeRulesNotInRuleSet</span><span class="p">();</span>
<span class="p">});</span>
<span class="c1">// Validate specific properties</span>
<span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">opt</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">opt</span><span class="p">.</span><span class="nf">IncludeProperties</span><span class="p">(</span><span class="s">"Forename"</span><span class="p">,</span> <span class="s">"Surname"</span><span class="p">);</span>
<span class="c1">// or</span>
<span class="n">opt</span><span class="p">.</span><span class="nf">IncludeProperties</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Surname</span><span class="p">,</span> <span class="n">x</span><span class="p">.</span><span class="n">Forename</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// Throw exceptions on failure</span>
<span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">opt</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">opt</span><span class="p">.</span><span class="nf">ThrowOnFailures</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>
<p>These options can be combined together in the same validation run eg:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Validate specific rulesets</span>
<span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">opt</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">opt</span><span class="p">.</span><span class="nf">IncludeRuleSets</span><span class="p">(</span><span class="s">"MyRuleSet"</span><span class="p">,</span> <span class="s">"SomeOtherRuleSet"</span><span class="p">).</span><span class="nf">IncludeRulesNotInRuleSet</span><span class="p">();</span>
<span class="n">opt</span><span class="p">.</span><span class="nf">ThrowOnFailures</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>
<h3 id="cascademode-impprovements">CascadeMode impprovements</h3>
<p>FluentValidation has supported the concept of a “CascadeMode” since version 1.2. The cascade mode allows a rule to stop after the first failure. For example:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">FirstName</span><span class="p">).</span><span class="nf">Cascade</span><span class="p">(</span><span class="n">CascadeMode</span><span class="p">.</span><span class="n">StopOnFirstFailure</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">().</span><span class="nf">NotEqual</span><span class="p">(</span><span class="s">"foo"</span><span class="p">);</span>
</code></pre></div></div>
<p>In this case the <code class="language-plaintext highlighter-rouge">NotEqual</code> will not run if the <code class="language-plaintext highlighter-rouge">NotNull</code> fails. This logic has always been in place, and can also be set at the validator level:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">MyValidator</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">CascadeMode</span> <span class="p">=</span> <span class="n">CascadeMode</span><span class="p">.</span><span class="n">StopOnFirstFailure</span><span class="p">;</span>
<span class="c1">// The NotEqual on this rule will not run if the NotNull fails.</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">FirstName</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">().</span><span class="nf">NotEqual</span><span class="p">(</span><span class="s">"foo"</span><span class="p">);</span>
<span class="c1">// The NotEqual on this will will also not run if its NotNull fails, </span>
<span class="c1">// but the NotNull will always run regardless of the rules on FirstName above</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">LastName</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">().</span><span class="nf">NotEqual</span><span class="p">(</span><span class="s">"foo"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Setting this at the validator level is the equivalent of setting it against each of the rules. However, the behaviour was often misleading for users of the library.
Many users have assumed this means “stop immediately if <em>any</em> rule produces a failure”, ie the whole validator could would only ever generate 0 or 1 validation errors.
However this wasn’t the case, this actually meant each call to <code class="language-plaintext highlighter-rouge">RuleFor</code> would only ever generate 0 or 1 failures (as <code class="language-plaintext highlighter-rouge">CascadeMode.StopOnFirstFailure</code> only ever applies at the rule-level, even when set on the validator).
This has caused a lot of confusion over the years.</p>
<p>To resolve this we decided to introduce a new behaviour which is consistent with what users were expecting, and allows for a true “fail-fast” behaviour. The new option (<code class="language-plaintext highlighter-rouge">CascadeMode.Stop</code>) will act as a true fail-fast. When set at the validator level:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="nf">MyValidator</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// This whole validator class will only ever produce 1 failure at most.</span>
<span class="k">this</span><span class="p">.</span><span class="n">CascadeMode</span> <span class="p">=</span> <span class="n">CascadeMode</span><span class="p">.</span><span class="n">Stop</span><span class="p">;</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">FirstName</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">().</span><span class="nf">NotEqual</span><span class="p">(</span><span class="s">"foo"</span><span class="p">);</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">LastName</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">().</span><span class="nf">NotEqual</span><span class="p">(</span><span class="s">"foo"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The existing <code class="language-plaintext highlighter-rouge">StopOnFirstFailure</code> option will remain unchanged for backwards compatibility, but has been marked as deprecated.</p>
<h3 id="async-test-helper">Async test helper</h3>
<p>The <a href="https://docs.fluentvalidation.net/en/latest/testing.html">test helper</a> now has asynchronous overloads available.</p>
<p><a href="https://github.com/FluentValidation/FluentValidation/releases/tag/9.1.1">View the full release notes to see other fixes</a>.</p>FluentValidation 9.1 is now available. This release contains a few new features and some bug fixes. View the full release notes. A few highlights:FluentValidation 9 released2020-07-06T00:00:00+00:002020-07-06T00:00:00+00:00https://jeremyskinner.co.uk/2020/07/06/fluentvalidation-9-released<p>FluentValidation 9 is <a href="https://nuget.org/packages/fluentvalidation">now available</a>. This release contains several new features and bug fixes, as well as some breaking changes. Please read the <a href="https://docs.fluentvalidation.net/en/latest/upgrading-to-9.html">upgrade guide</a> and the <a href="https://github.com/FluentValidation/FluentValidation/releases/tag/9.0.0">changelog</a> for full details, but here are a few things to be aware of:</p>
<h3 id="new-testhelper-features">New TestHelper features</h3>
<p>FluentValidation’s unit-testing functionality has been improved with several new <a href="https://docs.fluentvalidation.net/en/latest/testing.html#advanced-test-extensions">advanced test extensions</a>. Additional improvements here for use with asynchronous validators are planned for 9.1.</p>
<h3 id="improvements-to-aspnet-core-setup">Improvements to ASP.NET Core setup</h3>
<p>When auto-registering validators in ASP.NET Core applications, you can now optionally filter out types you don’t want to be registered:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddMvc</span><span class="p">().</span><span class="nf">AddFluentValidation</span><span class="p">(</span><span class="n">fv</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">fv</span><span class="p">.</span><span class="n">RegisterValidatorsFromAssemblyContaining</span><span class="p"><</span><span class="n">MyValidator</span><span class="p">>(</span><span class="n">scanResult</span> <span class="p">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="n">scanResult</span><span class="p">.</span><span class="n">ValidatorType</span> <span class="p">!=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">SomeValidatorToExclude</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre></div></div>
<h3 id="notequalequal-now-perform-ordinal-comparisons">NotEqual/Equal now perform ordinal comparisons</h3>
<p>When using the <code class="language-plaintext highlighter-rouge">Equal</code> or <code class="language-plaintext highlighter-rouge">NotEqual</code> validators against a string property, these will now perform ordinal comparisons (rather than culture-specific comparisons). This fixes a regression that was introduced way back in FluentValidation 4. If you need to perform a culture-specific comparison, you must now explicitly specify this. <a href="https://docs.fluentvalidation.net/en/latest/built-in-validators.html#equal-validator">See the API docs for details</a></p>
<h3 id="default-email-validation-mode">Default Email Validation Mode</h3>
<p>This is a subtle behaviour change. Prior to version 9.0 FluentValidation used a regular expression for email validation. This has been changed to a simple check for the presence of [something]@[something]. Although this is a more naive check, this brings FluentValidation’s email validation in-line with the .NET Core <code class="language-plaintext highlighter-rouge">EmailAddressAttribute</code>, and also follows the recommendation that regular expressions shouldn’t be used for email addresses. If you need to preserve the previous behaviour, you can supply an optional parameter to use the old behaviour: <code class="language-plaintext highlighter-rouge">RuleFor(customer => customer.Email).EmailAddress(EmailValidationMode.Net4xRegex);</code></p>
<p><a href="https://docs.fluentvalidation.net/en/latest/upgrading-to-9.html#default-email-validation-mode-changed">See here for more details on this change</a> and the <a href="https://docs.fluentvalidation.net/en/latest/built-in-validators.html?highlight=email#email-validator">API documentation here</a></p>
<h3 id="updates-to-the-scaleprecision-validator">Updates to the ScalePrecision validator</h3>
<p>The algorithm used by the ScalePrecision validator has been <a href="https://docs.fluentvalidation.net/en/latest/upgrading-to-9.html#changes-to-the-scaleprecisionvalidator">updated to match the behaviour of SQL Server (and other RDBMS solutions)</a></p>
<h3 id="removal-of-non-generic-validate-overload-and-non-generic-validationcontext-class">Removal of non-generic Validate overload and non-generic ValidationContext class</h3>
<p>The <code class="language-plaintext highlighter-rouge">IValidator.Validate(object model)</code> overload has been removed to improve type safety. If you were using this method before, you should change your code to use the overload that accepts an <code class="language-plaintext highlighter-rouge">IValidationContext</code> instead:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">var</span> <span class="n">context</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ValidationContext</span><span class="p"><</span><span class="kt">object</span><span class="p">>(</span><span class="n">model</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="supported-frameworks">Supported frameworks</h3>
<p>Several old versions of the .NET Framework and .NET Core are no longer supported. The supported platforms list for the core FluentValidation library is now:</p>
<ul>
<li>.NET Core 2.0 or newer</li>
<li>.NET Framework 4.6.1 or newer</li>
</ul>
<p>For the ASP.NET integration, the supported platforms are:</p>
<ul>
<li>.NET Core 3.1</li>
<li>.NET Core 2.1</li>
</ul>
<p>If you need to run FluentValidation on older versions of .NET Framework (<4.6.1) or obsolete versions of .NET Core (2.0, 2.2, 3.0) then you’ll need to stick with FluentValidation 8.6.</p>
<h3 id="several-changes-to-the-internal-api">Several changes to the internal API</h3>
<p>This will not affect normal users of the library, but if you have built any custom advanced extensions then you should <a href="https://docs.fluentvalidation.net/en/latest/upgrading-to-9.html">See the upgrade guide to check if they affect you</a></p>
<h3 id="removal-of-several-deprecated-features">Removal of several deprecated features</h3>
<p>Several features that were deprecated in FluentValidation 8.x have been removed in 9.0. <a href="https://docs.fluentvalidation.net/en/latest/upgrading-to-9.html">See the upgrade guide for a complete list</a></p>FluentValidation 9 is now available. This release contains several new features and bug fixes, as well as some breaking changes. Please read the upgrade guide and the changelog for full details, but here are a few things to be aware of:FluentValidation 8.2 released2019-04-09T00:00:00+00:002019-04-09T00:00:00+00:00https://jeremyskinner.co.uk/2019/04/09/fluentvalidation-82-released<p>FluentValidation 8.2 is <a href="https://nuget.org/packages/fluentvalidation">now available</a>. This release contains several new features and bug fixes:</p>
<h3 id="aspnet-core-di-extensions-for-child-validators">ASP.NET Core DI Extensions for Child Validators</h3>
<p>It’s always been possible to inject child validators when working with a Dependency Injection container such as ASP.NET Core’s Service Provider. Typically, you’d do this by injecting the child validator into the parent’s constructor and then calling <code class="language-plaintext highlighter-rouge">SetValidator</code>:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PersonValidator</span> <span class="p">:</span> <span class="n">AbstractValidator</span><span class="p"><</span><span class="n">Person</span><span class="p">></span> <span class="p">{</span>
<span class="k">public</span> <span class="nf">PersonValidator</span><span class="p">(</span><span class="n">AddressValidator</span> <span class="n">addressValidator</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Address</span><span class="p">).</span><span class="nf">SetValidator</span><span class="p">(</span><span class="n">addressValidator</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With FluentValidation 8.2, you can optionally use the <code class="language-plaintext highlighter-rouge">InjectValidator</code> method instead:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PersonValidator</span> <span class="p">:</span> <span class="n">AbstractValidator</span><span class="p"><</span><span class="n">Person</span><span class="p">></span> <span class="p">{</span>
<span class="k">public</span> <span class="nf">PersonValidator</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Address</span><span class="p">).</span><span class="nf">InjectValidator</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In this case, FluentValidation will attempt to resolve an instance of <code class="language-plaintext highlighter-rouge">IValidator<T></code> from ASP.NET’s service collection, where <code class="language-plaintext highlighter-rouge">T</code> is the same type as the property being validated (so in this case it’ll look for an implementation of <code class="language-plaintext highlighter-rouge">IValidator<Address></code> registered with the container).
If you need to explicitly specify the type, then this can be done with the other overload of <code class="language-plaintext highlighter-rouge">InjectValidator</code> which accepts a func referencing the service provider:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">PersonValidator</span> <span class="p">:</span> <span class="n">AbstractValidator</span><span class="p"><</span><span class="n">Person</span><span class="p">></span> <span class="p">{</span>
<span class="k">public</span> <span class="nf">PersonValidator</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Address</span><span class="p">).</span><span class="nf">InjectValidator</span><span class="p">((</span><span class="n">services</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span> <span class="p">=></span> <span class="n">services</span><span class="p">.</span><span class="n">GetService</span><span class="p"><</span><span class="n">SomeOtherAddressValidator</span><span class="p">>());</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that these methods are only available when using the automatic validation that’s part of <a href="https://fluentvalidation.net/aspnet">FluentValidation’s ASP.NET Core integration</a>. These methods will not be available to use if you’re invoking validators manually, or using FluentValidation outside of ASP.NET Core.</p>
<h3 id="aspnet-core-di-extensions-for-registering-validators">ASP.NET Core DI Extensions for registering validators</h3>
<p>There are several new methods for registering validators with the ASP.NET service provider. These are extension methods on <code class="language-plaintext highlighter-rouge">IServiceCollection</code>:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">AddValidatorsFromAssemblies</span><span class="p">(</span><span class="k">this</span> <span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">,</span> <span class="n">IEnumerable</span><span class="p"><</span><span class="n">Assembly</span><span class="p">></span> <span class="n">assemblies</span><span class="p">,</span> <span class="n">ServiceLifetime</span> <span class="n">lifetime</span> <span class="p">=</span> <span class="n">ServiceLifetime</span><span class="p">.</span><span class="n">Transient</span><span class="p">)</span><span class="n">AddValidatorsFromAssembly</span>
<span class="nf">AddValidatorsFromAssembly</span><span class="p">(</span><span class="k">this</span> <span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">,</span> <span class="n">Assembly</span> <span class="n">assembly</span><span class="p">,</span> <span class="n">ServiceLifetime</span> <span class="n">lifetime</span> <span class="p">=</span> <span class="n">ServiceLifetime</span><span class="p">.</span><span class="n">Transient</span><span class="p">)</span>
<span class="nf">AddValidatorsFromAssemblyContaining</span><span class="p">(</span><span class="k">this</span> <span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">,</span> <span class="n">Type</span> <span class="n">type</span><span class="p">,</span> <span class="n">ServiceLifetime</span> <span class="n">lifetime</span> <span class="p">=</span> <span class="n">ServiceLifetime</span><span class="p">.</span><span class="n">Transient</span><span class="p">)</span>
<span class="n">AddValidatorsFromAssemblyContaining</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="k">this</span> <span class="n">IServiceCollection</span> <span class="n">services</span><span class="p">,</span> <span class="n">ServiceLifetime</span> <span class="n">lifetime</span> <span class="p">=</span> <span class="n">ServiceLifetime</span><span class="p">.</span><span class="n">Transient</span><span class="p">)</span>
</code></pre></div></div>
<p>Typically you would not need to use these directly as the standard way of registering validators with the container would be to call the <code class="language-plaintext highlighter-rouge">Register...</code> methods as part of <code class="language-plaintext highlighter-rouge">AddFluentValidation</code> in application startup:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">services</span><span class="p">.</span><span class="nf">AddMvc</span><span class="p">().</span><span class="nf">AddFluentValidation</span><span class="p">(</span><span class="n">fv</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">fv</span><span class="p">.</span><span class="n">RegisterValidatorsFromAssemblyContaining</span><span class="p"><</span><span class="n">MyValidator</span><span class="p">>();</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Internally, the methods part of <code class="language-plaintext highlighter-rouge">AddFluentValidation</code> now make use of the new service collection extensions.</p>
<h3 id="ruleforforeach-as-an-alternative-to-ruleforeach">RuleFor.ForEach as an alternative to RuleForEach.</h3>
<p>FluentValidation has supported <code class="language-plaintext highlighter-rouge">RuleForEach</code> for several years, which allows a rule to be applied to all (or some) items in a collection. As an alternative to using <code class="language-plaintext highlighter-rouge">RuleForEach</code>, it’s now possible to call <code class="language-plaintext highlighter-rouge">ForEach</code> as part of a regular <code class="language-plaintext highlighter-rouge">RuleFor</code>.
With this approach you can combine rules that act upon the entire collection with rules which act upon individual elements within the collection. For example:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Orders</span><span class="p">)</span>
<span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Count</span> <span class="p"><=</span> <span class="m">10</span><span class="p">).</span><span class="nf">WithMessage</span><span class="p">(</span><span class="s">"No more than 10 orders are allowed"</span><span class="p">);</span>
<span class="nf">RuleForEach</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Orders</span><span class="p">)</span>
<span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="n">order</span> <span class="p">=></span> <span class="n">order</span><span class="p">.</span><span class="n">Total</span> <span class="p">></span> <span class="m">0</span><span class="p">).</span><span class="nf">WithMessage</span><span class="p">(</span><span class="s">"Orders must have a total of more than 0"</span><span class="p">);</span>
</code></pre></div></div>
<p>The above 2 rules could be re-written as:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Orders</span><span class="p">)</span>
<span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Orders</span><span class="p">.</span><span class="n">Count</span> <span class="p"><=</span> <span class="m">10</span><span class="p">).</span><span class="nf">WithMessage</span><span class="p">(</span><span class="s">"No more than 10 orders are allowed"</span><span class="p">)</span>
<span class="p">.</span><span class="nf">ForEach</span><span class="p">(</span><span class="n">orderRule</span> <span class="p">=></span> <span class="p">{</span>
<span class="n">orderRule</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="n">order</span> <span class="p">=></span> <span class="n">order</span><span class="p">.</span><span class="n">Total</span> <span class="p">></span> <span class="m">0</span><span class="p">).</span><span class="nf">WithMessage</span><span class="p">(</span><span class="s">"Orders must have a total of more than 0"</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>
<p>I personally think that using 2 rules is clearer and easier to read, but the option of combining them is now available with the <code class="language-plaintext highlighter-rouge">ForEach</code> method.</p>
<h3 id="default-interceptor-implementation">Default interceptor implementation</h3>
<p>It’s now possible to register a default implementation of <code class="language-plaintext highlighter-rouge">IValidatorInterceptor</code> with the service provider which will be used for all calls to FluentValidation by the automatic ASP.NET integration. <a href="https://fluentvalidation.net/aspnet#validator-interceptors">See the documentation for more details on interceptors</a>.</p>
<p>Note that this is only available with ASP.NET Core, not with MVC5 or WebApi2.</p>
<h3 id="withlocalizedmessage-is-deprecated">WithLocalizedMessage is deprecated</h3>
<p>The overload of <code class="language-plaintext highlighter-rouge">WithMessage</code> that takes a callback/func should be used instead.</p>
<h3 id="mvc5-and-webapi2-integration-is-deprecated">MVC5 and WebApi2 integration is deprecated</h3>
<p>Support for MVC5 and WebApi2 is now considered legacy. Going forward, only the ASP.NET Core integration will receive updates. Note that the current MVC5 and WebApi2 packages will continue to work just fine as part of FluentValidation 8.x but won’t receive any further updates and won’t ship as part of 9.0.</p>FluentValidation 8.2 is now available. This release contains several new features and bug fixes:FluentValidation 8.1 released2018-12-06T00:00:00+00:002018-12-06T00:00:00+00:00https://jeremyskinner.co.uk/2018/12/06/fluentvalidation-81-released<p>FluentValidation 8.1 is <a href="https://nuget.org/packages/fluentvalidation">now available</a>. This release contains several new features and bug fixes:</p>
<h3 id="string-formatting-in-message-placeholders">String Formatting in Message Placeholders</h3>
<p>FluentValidation has always supported placeholders within error messages, such as <code class="language-plaintext highlighter-rouge">{PropertyValue}</code>:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Age</span><span class="p">).</span><span class="nf">GreaterThan</span><span class="p">(</span><span class="m">18</span><span class="p">).</span><span class="nf">WithMessage</span><span class="p">(</span><span class="s">"Must be older than 18 years. You entered {PropertyValue} years."</span><span class="p">);</span>
</code></pre></div></div>
<p>Now you can also specify standard .NET formatting strings as part of the placeholder, such as <code class="language-plaintext highlighter-rouge">{PropertyValue:d}</code> or <code class="language-plaintext highlighter-rouge">{PropertyValue:p1}</code> etc.</p>
<h3 id="overriding-indexers-for-collection-rules">Overriding Indexers for Collection Rules</h3>
<p>Collection rules built using <code class="language-plaintext highlighter-rouge">RuleForEach</code> now allow you to override the indexer. For example, imagine you defined a collection rule against an collection of Address lines:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">RuleForEach</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">AddressLines</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
</code></pre></div></div>
<p>…Then the validator would generate validation failures for properties <code class="language-plaintext highlighter-rouge">person.AddressesLines[0]</code> and <code class="language-plaintext highlighter-rouge">person.AddressLines[1]</code> etc. The indexer would always be the numeric index within the collection surrounded by square brackets. This can now be overridden to use something different, or remove the indexer completely. Eg:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v</span><span class="p">.</span><span class="nf">RuleForEach</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">AddressLines</span><span class="p">)</span>
<span class="p">.</span><span class="nf">OverrideIndexer</span><span class="p">((</span><span class="n">x</span><span class="p">,</span> <span class="n">collection</span><span class="p">,</span> <span class="n">element</span><span class="p">,</span> <span class="n">index</span><span class="p">)</span> <span class="p">=></span> <span class="s">"<"</span> <span class="p">+</span> <span class="n">index</span> <span class="p">+</span> <span class="s">">"</span><span class="p">)</span>
<span class="p">.</span><span class="nf">NotNull</span><span class="p">()</span>
</code></pre></div></div>
<p>This would use angle brackets rather than square brackets in the generated indexer.</p>
<h3 id="error-code-can-now-be-used-to-select-a-different-default-error-message">Error code can now be used to select a different default error message.</h3>
<p>You can now use error codes to point to a particular error message stored in the default language manager. For example, the default ‘NotNull’ validation message has a code of <code class="language-plaintext highlighter-rouge">NotNullValidator</code> and a message of <code class="language-plaintext highlighter-rouge">'{PropertyName}' must not be empty.</code> If you want to use this message from inside a different validator (eg for a custom validator), you would have to hard-code the message again:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Name</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span> <span class="c1">//defaults to '{PropertyName}' must not be empty.</span>
<span class="c1">// `Must` has its own default error message, so if you want to use the NotNull message you'd have to specify it explicitly</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Name</span><span class="p">).</span><span class="nf">Must</span><span class="p">(</span><span class="n">name</span> <span class="p">=></span> <span class="n">name</span> <span class="p">!=</span> <span class="k">null</span><span class="p">).</span><span class="nf">WithMessage</span><span class="p">(</span><span class="s">"'{PropertyName}' must not be empty."</span><span class="p">);</span>
</code></pre></div></div>
<p>However now you can select a message based on error code:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RuleFor(x +> x.Name).Must(name => name != null).WithErrorCode("NotNullValidator");
</code></pre></div></div>
<h3 id="test-helper-improvements">Test Helper improvements</h3>
<p>Additional details are now shown in the output for <code class="language-plaintext highlighter-rouge">ShouldNotHaveValidationErrorFor</code>.</p>
<h3 id="conditional-validation-improvements">Conditional validation improvements</h3>
<p>If you use the top-level <code class="language-plaintext highlighter-rouge">When</code> or <code class="language-plaintext highlighter-rouge">Unless</code> methods to wrap multiple rules, the condition check is now cached and only executed once.</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// The IsStudnet check will now only be executed once.</span>
<span class="nf">When</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">IsStudent</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">StudentNumber</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Courses</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>
<p>There’s also a new <code class="language-plaintext highlighter-rouge">Otherwise</code> method that can now be chanined onto a <code class="language-plaintext highlighter-rouge">When</code> or <code class="language-plaintext highlighter-rouge">Unless</code> to do its opposite, without having to set up a second <code class="language-plaintext highlighter-rouge">When</code> call with the opposite condition:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">When</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">IsStudent</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">StudentNumber</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Courses</span><span class="p">).</span><span class="nf">NotNull</span><span class="p">();</span>
<span class="p">}).</span><span class="nf">Otherwise</span><span class="p">(()</span> <span class="p">=></span> <span class="p">{</span>
<span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">StudentNumber</span><span class="p">).</span><span class="nf">Null</span><span class="p">();</span>
<span class="p">});</span>
</code></pre></div></div>
<h3 id="other-minor-changes">Other minor changes</h3>
<ul>
<li>MVC 5’s <code class="language-plaintext highlighter-rouge">CustomizeValidatorAttribute</code> now has the <code class="language-plaintext highlighter-rouge">Skip</code> property, like WebApi and AspNetCore, allowing auto-validation to be skipped for specific action parameters.</li>
<li>Japanese language translation</li>
<li>The overload of <code class="language-plaintext highlighter-rouge">OverridePropertyName</code> that takes an expression can now use an expression with any return value, not just strings.</li>
<li>Minor wording changes to the default English error messages.</li>
</ul>FluentValidation 8.1 is now available. This release contains several new features and bug fixes:Updating .NET Core inside AppVeyor2018-08-28T00:00:00+00:002018-08-28T00:00:00+00:00https://jeremyskinner.co.uk/2018/08/28/updating-dotnetcore-in-appveyor<p>I currently use <a href="https://appveyor.com">appveyor</a> to run Continuous Integration builds for <a href="https://fluentvalidation.net">FluentValidation</a>. It’s really easy to get started with appveyor as the build images come pre-installed with most tools you’d want to use as part of a build. The downside is it’s not easy to specify which version of a particular tool you want to use, and appveyor often lags behind with new releases.</p>
<p>For example, for FluentValidation I want the master branch to be built against the current LTS version of the .NET Core SDK (2.1.400), and I want my vNext branch to build against 2.2-preview1. But appveyor only has version 2.1.300 installed, so both branches need a new version of the .NET SDK.</p>
<p>Based on <a href="https://andrewlock.net/building-asp-net-core-2-0-preview-2-packages-on-appveyor/">this post</a> by Andrew Lock, I updated the FluentValidation build script to download an install a newer .NET Core SDK if needed. The build script uses powershell with the <a href="https://github.com/jeremyskinner/posh-build">posh-build</a> helpers for defining targets:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">target</span><span class="w"> </span><span class="nx">install-dotnet-core</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="c"># Ensures that .net core is up to date.</span><span class="w">
</span><span class="c"># first get the required version from global.json</span><span class="w">
</span><span class="nv">$json</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ConvertFrom-Json</span><span class="w"> </span><span class="p">(</span><span class="n">Get-Content</span><span class="w"> </span><span class="s2">"</span><span class="nv">$path</span><span class="s2">/global.json"</span><span class="w"> </span><span class="nt">-Raw</span><span class="p">)</span><span class="w">
</span><span class="nv">$required_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$json</span><span class="o">.</span><span class="nf">sdk</span><span class="o">.</span><span class="nf">version</span><span class="w">
</span><span class="c"># Running dotnet --version stupidly fails if the required SDK version is higher </span><span class="w">
</span><span class="c"># than the currently installed version. So move global.json out the way </span><span class="w">
</span><span class="c"># and then put it back again </span><span class="w">
</span><span class="n">Rename-Item</span><span class="w"> </span><span class="s2">"</span><span class="nv">$path</span><span class="s2">/global.json"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$path</span><span class="s2">/global.json.bak"</span><span class="w">
</span><span class="nv">$current_version</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="n">dotnet</span><span class="w"> </span><span class="nt">--version</span><span class="p">)</span><span class="w">
</span><span class="n">Rename-Item</span><span class="w"> </span><span class="s2">"</span><span class="nv">$path</span><span class="s2">/global.json.bak"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$path</span><span class="s2">/global.json"</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Required .NET version: </span><span class="nv">$required_version</span><span class="s2"> Installed: </span><span class="nv">$current_version</span><span class="s2">"</span><span class="w">
</span><span class="kr">if</span><span class="w"> </span><span class="p">(</span><span class="nv">$current_version</span><span class="w"> </span><span class="o">-lt</span><span class="w"> </span><span class="nv">$required_version</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="c"># Current installed version is too low.</span><span class="w">
</span><span class="c"># Install new version as a local only dependency. </span><span class="w">
</span><span class="nv">$urlCurrent</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"https://dotnetcli.blob.core.windows.net/dotnet/Sdk/</span><span class="nv">$required_version</span><span class="s2">/dotnet-sdk-</span><span class="nv">$required_version</span><span class="s2">-win-x64.zip"</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Installing .NET Core </span><span class="nv">$required_version</span><span class="s2"> from </span><span class="nv">$urlCurrent</span><span class="s2">"</span><span class="w">
</span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">DOTNET_INSTALL_DIR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"</span><span class="nv">$path</span><span class="s2">/.dotnetsdk"</span><span class="w">
</span><span class="n">New-Item</span><span class="w"> </span><span class="nt">-Type</span><span class="w"> </span><span class="nx">Directory</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">DOTNET_INSTALL_DIR</span><span class="w"> </span><span class="nt">-Force</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">Out-Null</span><span class="w">
</span><span class="p">(</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Net.WebClient</span><span class="p">)</span><span class="o">.</span><span class="nf">DownloadFile</span><span class="p">(</span><span class="nv">$urlCurrent</span><span class="p">,</span><span class="w"> </span><span class="s2">"dotnet.zip"</span><span class="p">)</span><span class="w">
</span><span class="n">Write-Host</span><span class="w"> </span><span class="s2">"Unzipping to </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">DOTNET_INSTALL_DIR</span><span class="s2">"</span><span class="w">
</span><span class="n">Add-Type</span><span class="w"> </span><span class="nt">-AssemblyName</span><span class="w"> </span><span class="nx">System.IO.Compression.FileSystem</span><span class="p">;</span><span class="w"> </span><span class="p">[</span><span class="n">System.IO.Compression.ZipFile</span><span class="p">]::</span><span class="n">ExtractToDirectory</span><span class="p">(</span><span class="s2">"dotnet.zip"</span><span class="p">,</span><span class="w"> </span><span class="nv">$</span><span class="nn">env</span><span class="p">:</span><span class="nv">DOTNET_INSTALL_DIR</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>Here I do the following:</p>
<ul>
<li>Check which version of .net core is needed based on what’s defined in my global.json</li>
<li>Check the current .net core installed version (working around that <code class="language-plaintext highlighter-rouge">dotnet --version</code> fails if there’s a mismatch between required and expected version)</li>
<li>Download the new SDK and unzip it to a local directory</li>
<li>Update the environment PATH variable so that the new version is used whenever <code class="language-plaintext highlighter-rouge">dotnet</code> is called.</li>
</ul>
<p>This script can then be called as part of the <code class="language-plaintext highlighter-rouge">install</code> section of your appveyor.yml.</p>
<p>The full example is available in the <a href="https://github.com/JeremySkinner/FluentValidation/blob/668e462a8056f4c334f144644aa31d20c9d7c9a9/build.ps1#L117-L155">FluentValidation repository</a>.</p>I currently use appveyor to run Continuous Integration builds for FluentValidation. It’s really easy to get started with appveyor as the build images come pre-installed with most tools you’d want to use as part of a build. The downside is it’s not easy to specify which version of a particular tool you want to use, and appveyor often lags behind with new releases.FluentValidation 8.0 released2018-08-16T00:00:00+00:002018-08-16T00:00:00+00:00https://jeremyskinner.co.uk/2018/08/16/fluentvalidation-8-released<p>FluentValidation is out and <a href="http://nuget.org/packages/fluentvalidation">available to download from Nuget</a>. This is a major release with several breaking change, so please make sure you read the <a href="https://fluentvalidation.net/upgrading-to-8.html">upgrade notes</a> before upgrading.</p>
<h3 id="validating-properties-by-path">Validating properties by path</h3>
<p>You can now validate specific properties using a full path, eg:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">validator</span><span class="p">.</span><span class="nf">Validate</span><span class="p">(</span><span class="n">customer</span><span class="p">,</span> <span class="s">"Address.Line1"</span><span class="p">,</span> <span class="s">"Address.Line2"</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="validating-a-specific-ruleset-with-setvalidator">Validating a specific ruleset with SetValidator</h3>
<p>Previously, if you defined a child validator with <code class="language-plaintext highlighter-rouge">SetValidator</code>, then whichever ruleset you invoked on the parent validator will cascade to the child validator.
Now you can explicitly define which ruleset will run on the child:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">RuleFor</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">.</span><span class="n">Address</span><span class="p">).</span><span class="nf">SetValidator</span><span class="p">(</span><span class="k">new</span> <span class="nf">AddressValidator</span><span class="p">(),</span> <span class="s">"myRuleset"</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="attrbiutedvalidatorfactory-has-been-moved-to-a-separate-package">AttrbiutedValidatorFactory has been moved to a separate package</h3>
<p>The <code class="language-plaintext highlighter-rouge">ValidatorAttribute</code> and the <code class="language-plaintext highlighter-rouge">AttributedValidatorFactory</code> were typically used in MVC/WebApi projects to wire models to their validators. This is no longer recommended when usign AspNetCore as the built-in Service Provider is a better alternative.
These classes can still be used by explicitly installing the <code class="language-plaintext highlighter-rouge">FluentValidation.ValidatorAttribute</code> package. Note this package is installed by default if you are using the legacy MVC5/WebApi integration rather than AspNetCore.</p>
<h3 id="setcollectionvalidator-is-deprecated">SetCollectionValidator is deprecated</h3>
<p><code class="language-plaintext highlighter-rouge">RuleForEach</code> provides a more flexible syntax for the same result.</p>
<h3 id="async-changes">Async changes</h3>
<p>Internally, the asynchronous validation API has been cleaned up thanks to <code class="language-plaintext highlighter-rouge">await\async</code>. From a consumer’s point of view, the asynchronous methods should all continue to work as before with the exception of some methods that previously didn’t take a <code class="language-plaintext highlighter-rouge">CancellationToken</code> that now do.</p>
<p>The full changelog is <a href="https://github.com/JeremySkinner/FluentValidation/blob/master/Changelog.txt">available here</a></p>FluentValidation is out and available to download from Nuget. This is a major release with several breaking change, so please make sure you read the upgrade notes before upgrading.Powershell SSH Connection manager2018-07-30T00:00:00+00:002018-07-30T00:00:00+00:00https://jeremyskinner.co.uk/2018/07/30/powershell-ssh-connection-manager<p>I’ve recently a connection manager for SSH connections to the <a href="https://github.com/dahlbyk/posh-sshell">posh-sshell</a> project.</p>
<h3 id="what-is-posh-sshell">What is Posh-Sshell?</h3>
<p>Posh-Sshell is a set of powershell scripts that making working with SSH agents and clients easier. These utilities were originally part of the <a href="https://github.com/dahlbyk/posh-git">posh-git</a> project, but have been separated into a separate module in preparation for the Posh-Git 1.0 release.</p>
<p>Posh-Sshell can be <a href="https://www.powershellgallery.com/packages/posh-sshell/0.2.0">downloaded from the powershell gallery</a> by running the following in a powershell prompt:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Install-Module</span><span class="w"> </span><span class="nx">Posh-Sshell</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">CurrentUser</span><span class="w">
</span></code></pre></div></div>
<p>Once installed, import the module with <code class="language-plaintext highlighter-rouge">Import-Module Posh-Shell</code> (you can add this to your powershell profile so you don’t need to run it every time you launch a new terminal).</p>
<h3 id="connection-manager">Connection Manager</h3>
<p>Since splitting the SSH functionality out of posh-git, I’ve been working on several new features the first of which is the Connection Manager.</p>
<p>The Connection Manager can be used to display a list of SSH connections as well as add/remove connections from the <code class="language-plaintext highlighter-rouge">~/.ssh/config</code> file. By running <code class="language-plaintext highlighter-rouge">Connect-Ssh</code>, you’ll be presented with a list of connections stored in your .ssh/config file:</p>
<p><img src="https://user-images.githubusercontent.com/90130/43395146-f9060402-93f4-11e8-8877-b9987006b632.png" alt="image" /></p>
<p>You can enter the number of the server into the prompt, and an SSH connection will be made to that server. If no username is specified in the configuration file, then you’ll also be prompted for a username.</p>
<h4 id="adding-a-new-connection">Adding a New Connection</h4>
<p>A new connection can be added by running <code class="language-plaintext highlighter-rouge">Add-SshConnection</code>. In its simplest form, it takes an alias and a URI, eg:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Add-SshConnection</span><span class="w"> </span><span class="nx">MyServer3</span><span class="w"> </span><span class="nx">myserver.mydomain.com</span><span class="w">
</span></code></pre></div></div>
<p>You can also specify additional common properties such as the username, either by using the <code class="language-plaintext highlighter-rouge">-User</code> parameter or using the <code class="language-plaintext highlighter-rouge">user@host</code> syntax:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># These are both the same</span><span class="w">
</span><span class="n">Add-SshConnection</span><span class="w"> </span><span class="nx">MyServer3</span><span class="w"> </span><span class="nx">myserver.mydomain.com</span><span class="w"> </span><span class="nt">-User</span><span class="w"> </span><span class="nx">jeremy</span><span class="w">
</span><span class="n">Add-SshConnection</span><span class="w"> </span><span class="nx">MyServer3</span><span class="w"> </span><span class="nx">jeremy</span><span class="err">@</span><span class="nx">myserver.mydomain.com</span><span class="w">
</span></code></pre></div></div>
<p>You can specify a custom key file by using <code class="language-plaintext highlighter-rouge">-IdentityFile <path></code> as well as parameters for configuring an SSH tunnel with <code class="language-plaintext highlighter-rouge">-LocalTunnelPort <port number></code> and <code class="language-plaintext highlighter-rouge">-RemoteTunnelPort <port number></code>.</p>
<p>Additional parameters can also be specified by supplying a hashtable to the <code class="language-plaintext highlighter-rouge">-AdditionalOptions</code> parameter.</p>
<p>After running <code class="language-plaintext highlighter-rouge">Add-SshConnection MyServer3 myserver.mydomain.com -User jeremy</code>, your .ssh/config file will contain the new entry:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host MyServer3
HostName myserver.mydomain.com
User jeremy
</code></pre></div></div>
<h3 id="removing-an-entry">Removing an entry</h3>
<p>Entries can be removed from the config file by running <code class="language-plaintext highlighter-rouge">Remove-SshConnection <name></code></p>I’ve recently a connection manager for SSH connections to the posh-sshell project.Thoughts on using Drupal2018-07-28T00:00:00+00:002018-07-28T00:00:00+00:00https://jeremyskinner.co.uk/2018/07/28/thoughts-on-moving-to-drupal<p>After 10 years working with ASP.NET and C#, I’ve spent the last few months working with Drupal 8 and PHP. The last time I worked with PHP was in 2005, and although the language is still full of inconsistencies, it’s nice to see that it’s a fully object-oriented language and supports more modern functionality like anonymous functions and traits.</p>
<p>Working with Drupal 8 has been quite a learning experience. Drupal is an extremely powerful platform, but has a huge learning curve. The documentation is pretty poor compared to the very well-written <a href="https://www.asp.net/learn">in-depth tutorials</a> and <a href="https://docs.microsoft.com">API docs</a> that ASP.NET provides. However It’s good to see that Drupal 8 has embraced a more MVC-based approach (by using Symfony controllers), dependency injection and plugins. Sadly the horrible hook-based extensibility system hasn’t completely gone away yet.</p>
<p>The hardest thing I’ve found with Drupal is following an application’s flow. Diving into an existing codebase and trying to find where a piece of functionality is implemented is something I found extremely challenging. For example a button on a form could be defined directly in a <code class="language-plaintext highlighter-rouge">Form</code> class, through a YAML file, or any number of hooks in any module within the application. I suppose this is the downside of an extremely flexible extensibility model.</p>
<p>I’ve also been enjoying getting much more into Linux and server management using the Bash CLI, something I’ve had very little experience with (my CLI of choice is usually Powershell)</p>
<p>Overall C#/.NET would still be my platform of choice, but learning something new and working on some very interesting projects with a great team is much more important to me than choice of language.</p>After 10 years working with ASP.NET and C#, I’ve spent the last few months working with Drupal 8 and PHP. The last time I worked with PHP was in 2005, and although the language is still full of inconsistencies, it’s nice to see that it’s a fully object-oriented language and supports more modern functionality like anonymous functions and traits.