# ASP.NET Core (.NET Framework) # Build and test ASP.NET Core projects targeting the full .NET Framework. # Add steps that publish symbols, save build artifacts, and more: # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core trigger: - feature/testability pool: vmImage: 'windows-latest' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' steps: - script: choco install sql-server-express - task: NuGetToolInstaller@1 - task: VisualStudioTestPlatformInstaller@1 displayName: 'Visual Studio Test Platform Installer' inputs: versionSelector: latestStable - task: NuGetCommand@2 inputs: restoreSolution: '$(solution)' - task: DotNetCoreCLI@2 displayName: Build inputs: command: build projects: '**/*.csproj' arguments: '--configuration Debug' # Update this to match your need - script: 'dotnet tool install --global dotnet-ef' displayName: 'Generate EF Code First Migrations SQL Script Update script' - script: 'dotnet ef migrations script -i -o %BUILD_ARTIFACTSTAGINGDIRECTORY%\migrate.sql --project .\SomeAcme\SomeAcme.csproj' displayName: 'Generate EF Code First Migrations migrate.sql' - script: 'sqlcmd -S .\SQLEXPRESS -Q "CREATE DATABASE [SomeAcmeDb]"' displayName: 'Create database SomeAcmeDb in Azure Devops SQL EXPRESS' - script: 'sqlcmd -i %BUILD_ARTIFACTSTAGINGDIRECTORY%\migrate.sql -S .\SQLEXPRESS -d SomeAcmeDb' displayName: ' Run migrate.sql on SQL EXPRESS in Azure Devops' # PowerShell # Run a PowerShell script on Linux, macOS, or Windows - task: PowerShell@2 inputs: targetType: 'inline' # Optional. Options: filePath, inline #filePath: # Required when targetType == FilePath #arguments: # Optional script: 'gci -recurse -filter *.dll' # Required when targetType == Inline #errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue #failOnStderr: false # Optional #ignoreLASTEXITCODE: false # Optional #pwsh: false # Optional #workingDirectory: # Optional - task: VSTest@2 displayName: 'VsTest - testAssemblies' inputs: testAssemblyVer2: | **\*SomeAcme.Tests.dll !**\*TestAdapter.dll !**\obj\** vsTestVersion: toolsInstaller testFiltercriteria: 'Category=IntegrationTest' runInParallel: false codeCoverageEnabled: false testRunTitle: 'XUnit tests SomeAcme solution integration test starting' failOnMinTestsNotRun: true rerunFailedTests: false
Sunday, 5 April 2020
Deploying an SQL Express database in Azure Devops pipeline with YAML and generating and updating the database with migrate scripts using EF Core Code First tools
Here a full example of how I achieved running Integration tests using Sql Express in Azure Devops. I had to use the YAML based pipelines so I could use simonauner's approach using Chocolatey to install Sql Express. Make note that I had to install EF Core tools since I use .Net Core 3.1 in this pipeline. Also note that I generate an EF Code First migration SQL file on the fly so that the rigged SQL Express instance is filled with contents.
Deploy SQL Express instance in Azure Devops, install and generate and run EF Code first migration sql script to update database with schema and seed data using EF Code First tools.
Etiketter:
Azure Devops,
Chocolatey,
Code First,
DevOps,
EF,
EF Core,
EF Core Code First,
entityframework,
Pipeline,
SQL Express,
YAML
Mocking HttpClient in .Net core 3 with Moq
I worked with HttpClient today and needed to write some unit tests. Here is how I did it.
First off, we need to define an HttpClientFactory interface like this:
public interface IHttpClientFactory { HttpClient CreateHttpClient(); }Your API code must then not directly create an http client, but pass in a IHttpClientFactory instance, allowing for tailored functionality such as mocking / unit testing. A default implementation could be:
public class DefaultHttpClientFactory : IHttpClientFactory { public HttpClient CreateHttpClient() { return new HttpClient(); } }And a mock implementation using Moq then looks like this:
using Moq; using Moq.Protected; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace SomeAcme.SomeApi.Test { public class MockHttpClientFactory : IHttpClientFactory { private HttpResponseMessage _httpContentMessage; public HttpClient CreateHttpClient() { return new HttpClient(CreateMockedHttpMessageHandler()); } private HttpMessageHandler CreateMockedHttpMessageHandler() { var handlerMock = new Mock<HttpMessageHandler>(MockBehavior.Strict); handlerMock .Protected() // Setup the PROTECTED method to mock .Setup<Task<HttpResponseMessage>>( "SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>() ) // prepare the expected response of the mocked http call .ReturnsAsync(SetupHttpResponseMessage()) .Verifiable(); handlerMock .Protected() // Setup the PROTECTED method to mock .Setup( "Dispose", ItExpr.IsAny<bool>() ) // prepare the expected response of the mocked http call .Verifiable(); return handlerMock.Object; } /// <summary> /// Set up the desired http response of unit tests /// </summary> /// <param name="statusCode"></param> /// <param name="responseJson"></param> public void SetHttpResponseMessage(HttpStatusCode statusCode, string responseJson) { _httpContentMessage = new HttpResponseMessage { StatusCode = statusCode, Content = new StringContent(responseJson) }; } private HttpResponseMessage SetupHttpResponseMessage() { if (_httpContentMessage != null) return _httpContentMessage; return new HttpResponseMessage() { StatusCode = HttpStatusCode.OK, Content = new StringContent("[{'id':1,'value':'1'}]") }; //return default dummy implementation for easy green testing of HttpClient.. } } }The key is to implement a mocked HttpMessageHandler which is then injected into the real HttpClient class. And also expose a method SetupHttpResponseMessage() allowing unit tests to specify desired http status code and json payload in the response. This is a very basic approach of unit testing HttpClient, so you can get code coverage of your HttpClient and related code in the application layer where your use it. Make sure you now inject the MockHttpClientFactory into your code and initialize it in your testse setting the SetupHttpResponseMessage. Tests should go a green again despite you actually use the HttpClient (with a mocked HttpMessageHandler) !
Friday, 3 April 2020
User friendly and Flexible numeric textbox for decimal numbers in WPF
Adapting WPF to allow a numeric textbox in WPF sounds very trivial, but it is not! A user friendly textbox in WPF couldhave different aspects.
It could be possible to:
- Only allow numbers or decimal separator
- Possible to specify number of digits for integer part and number of digits for decimal part
- Specify an empty default value
- Still rely on default components such as WPF Textbox
The following WPF Converter allows to restrict user input using ConvertBack method. Usually a developer implements the Convert method to transform output,
here we use ConvertBack to filter input to only allow numbers or decimal separator.
NumericFormatConverter
using Hemit.OpPlan.Common; using System; using System.Globalization; using System.Linq; using System.Threading; using System.Windows.Data; namespace Hemit.OpPlan.Client.Infrastructure.Converters { public class NumericFormatConverter : IValueConverter { public int IntegerLength { get; set; } public int DecimalLength { get; set; } public string DefaultValue { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value == null) return DefaultValue; string adjustedValue = AdjustValueToAcceptingFormat(value.ToString()); return adjustedValue; } private string AdjustValueToAcceptingFormat(string value) { if (string.IsNullOrEmpty(value)) return DefaultValue; char decimalSeparator = Thread.CurrentThread?.CurrentCulture?.NumberFormat?.NumberDecimalSeparator.ToCharArray().First() ?? ','; string[] compounds = value.Split(new[] { decimalSeparator }); if (compounds.Length <= 0) { return DefaultValue; //default to zero } if (compounds.Length == 1) { return NumericDigits(compounds[0], decimalSeparator); } return $"{NumericDigits(compounds[0], decimalSeparator).Truncate(3)}{decimalSeparator}{NumericDigits(compounds[1], decimalSeparator).Truncate(1)}"; } private static string NumericDigits(string input, char decimalSeparator) { return string.Join("", input.ToCharArray().Where(ch => Char.IsDigit(ch) || ch == decimalSeparator)).Truncate(3); } } }If you rely on MVVM, you can bind up a numeric value with a proxy property with datatype string for flexible input.
[State] [NotifyPropertyChanged(true)] [AffectsOtherProperty("BodyMassIndex")] [Range(0.0, 500, ErrorMessage = @"Max Weight is surpassed")] public double? Weight { get; set; } private string _weightInputValue; public string WeightInputValue { get { return _weightInputValue; } set { if (_weightInputValue != value) { _weightInputValue = value; double weight; if (double.TryParse(_weightInputValue, out weight)) { Weight = weight; } RaisePropertyChanged(() => WeightInputValue); } } }Never mind the Postsharp aspects for State, NotifyPropertyChanged and AffectsOtherProperty listed here, they are not necessary in your use case. But shows a real world scenario. In WPF frontend you can then bind up the WPF converter like this:
<TextBlock Grid.Row="7" Grid.Column="2" Margin="2" Text="Vekt (kg)" VerticalAlignment="Center" HorizontalAlignment="Left" /> <TextBox Grid.Row="7" Grid.Column="4" MaxLength="5" Height="24" behaviors:TextBoxRegExBehavior.RegularExpressionProperty="^\d+[,|.]*\d*$" FontWeight="Normal" HorizontalAlignment="Left" behaviors:TextBoxRegExBehavior.MaxLength="5" behaviors:TextBoxRegExBehavior.MustParseToType="{x:Type sys:Double}" behaviors:TextBoxRegExBehavior.EmptyValue="0" Margin="2,2,2,2" Width="40" Text="{Binding WeightInputValue, Converter={StaticResource numericFormatConverter}, ConverterCulture={x:Static gl:CultureInfo.CurrentCulture}, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=true}" Style="{StaticResource ReadOnlyTextBoxStyleHidingZero}" IsReadOnly="{Binding IsEditMode, Converter={StaticResource invertedBoolConverter}}" TabIndex="35" />Again, much custom stuff here, but the key thing to notice is that you only need to make sure you bind against the proxy property of type string, the WPF converter numericFormatConverter handles the formatting of the input.
<converters:NumericFormatConverter x:Key="numericFormatConverter" IntegerLength="3" DecimalLength="1" DefaultValue=""></converters:NumericFormatConverter>
Subscribe to:
Posts (Atom)