Tuesday, April 24, 2018

Using Dapper with Asp.net Core website on Mac

In this blog post we will create an Asp.Net Core 2.0 website with Visual Studio for the Mac and use Dapper to access a SQLite database.  Dapper is micro ORM written by the StackOverflow team.  It is meant to send the data from database to C# class.

To start create a new Asp.Net Core MVC app.



Now we need to add 2 NuGet packages.  Microsoft.Data.SQLite and Dapper




Next we are going to create an Interface for our DataAccess class.  I called it IDataAccess

    public interface IDataAccess
    {
        void InsertName(string Name);

        Name GetName(int id);

        void DeleteName(int Id);

        void UpdateName(int Id, string Name);
        
        List<Name> GetNames();
    }



Here is the class Name

    public class Name
    {
        public int Id { get; set; }

        public string name { get; set; }
    }



Now we are going to create an Class that Implements the IDataAccess interface

    public class SQLIteDataAccess : IDataAccess
    {
        private const string fileName = "MyDatabase.db";

        
private IDbConnection connection = null;

        public SQLIteDataAccess()
        {
            string connectionString = $"FileName={fileName}";
            connection = new SqliteConnection(connectionString);
            if(!File.Exists(fileName))
            {
                FileStream fileStream = File.Create(fileName);
                fileStream.Close();

                try
                {
                    connection.Open();
                    connection.Execute("Create Table Names(Id int Primary Key AUTOINCREMENT, Name Text) ");
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.Message);
                }
                finally
                {
                    connection.Close();
                }

            }
        }

        public void DeleteName(int Id)
        {
            try
            {
                connection.Open();
                connection.Execute("Delete from Names Where Id = @Id", new {Id = Id});
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
            finally
            {
                connection.Close();
            }
        }

        public System.Collections.Generic.List<Name> GetNames()
        {
            List<Name> results = null;
            try
            {
                connection.Open();
                results = connection.Query<Name>("Select * from Names").ToList();
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
            finally
            {
                connection.Close();
            }
            return results;
        }

        public void InsertName(string Name)
        {
            try
            {
                connection.Open();
                connection.Execute("Insert into Names (Name) value (@Name)", new { Name = Name });
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
            finally
            {
                connection.Close();
            }
        }

        public void UpdateName(int Id, string Name)
        {
            try
            {
                connection.Open();
                connection.Execute("Update Names set Name = @Name Where Id = @Id", 
                                   new { Name = Name, Id = Id });
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.Message);
            }
            finally
            {
                connection.Close();
            }
        }
    }



Ok lets talk about the Constructor before we move on to the Dapper code.

       public SQLIteDataAccess()
        {
            string connectionString = $"FileName={fileName}";
            connection = new SqliteConnection(connectionString);
            if(!File.Exists(fileName))
            {
                FileStream fileStream = File.Create(fileName);
                fileStream.Close();

                try
                {
                    connection.Open();
                    connection.Execute("Create Table Names(Id INTEGER Primary Key AUTOINCREMENT, Name Text) ");                }
                catch (Exception ex)
                {
                    Trace.WriteLine(ex.Message);
                }
                finally
                {
                    connection.Close();
                }

            }
        }

First thing I do is look and see if the database was created.  If it was not I am creating creating a new file with the database name I am looking for.  I store the file stream in a variable so I can close it.  If you don't do this the file will be locked and you won't be able to execute scripts on the database.

Next I create a connection to the database and run a create table script.  

connection.Execute("Create Table Names(Id INTEGER Primary Key AUTOINCREMENT, Name Text) ");


Now lets look at the dapper code.  It adds some methods to the connection that allows us to Execute a script or Query the database.

For example this command Queries the Database for all the records in the names table

results = connection.Query<Name>("Select * from Names").ToList();

To execute a command on the database with some parameters you do it like this

   connection.Execute("Insert into Names (Name) value (@Name)", new { Name = Name });

Please do not pass user input directly in to a database query I would use a parameter to reduce the chance of SQL injection



Finally we are going to add a controller to expose these methods via the web api

    [Route("api/[controller]")]
    public class SQLiteController : Controller
    {
        IDataAccess dataAccess;

        public SQLiteController(IDataAccess data)
        {
            dataAccess = data;
        }
        

        // GET: api/values
        [HttpGet]
        public List<Name> Get()
        {
            return dataAccess.GetNames();
        }

        // GET api/values/5
        [HttpGet("{id}")]
        public Name Get(int id)
        {
            return dataAccess.GetName(id);
        }

        // POST api/values
        [HttpPost]
        public void Post([FromBody]string value)
        {
            dataAccess.InsertName(value);
        }

        // PUT api/values/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
            dataAccess.UpdateName(id, value);
        }

        // DELETE api/values/5
        [HttpDelete("{id}")]
        public void Delete(int id)
        {
            dataAccess.DeleteName(id);
        }
    }


In the constructor I am injecting an Instance of the IDataAccess so I can use it in the controller.

       IDataAccess dataAccess;

        public SQLiteController(IDataAccess data)
        {
            dataAccess = data;
        }

To register the IDataAccess interface for dependency injection we do it in the Startup class

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddTransient<IDataAccess, SQLIteDataAccess>();
        }



Since I am doing this on the Mac I am going to use curl in the terminal window to test the service

Lets insert a name in the database

kentucker$ curl -H "Content-Type: application/json" -X POST -d '"Bill Gates"' http://localhost:19266/api/SQLite

List the Names


curl http://localhost:19266/api/SQLite






In this post I showed how to use Dapper with Asp.Net core. When I develop software that interacts with a database I like to develop using a local database.  Since SQL Server my server data base of choice is not available for the Mac. I decided to use SQLite for local development.  It is possible to use host SQL Server on a Mac in docker container but since it uses a lot of resources I figured SQLite would work.    In my next post I will show how to refactor the code to allow the class to allow the code the connect to either a SQL Server of SQLite database.


The source code is available on GITHUB


https://github.com/vb2ae/AspNetCoreSQLite

Sunday, April 8, 2018

Asp.Net Core 2 Configuration Values

In this blog post I will show you how to access the site settings in an asp.net core 2.0 website.  I am using Visual Studio for the Mac to doing the coding but it should work the same in the windows version of Visual Studio.

Configuration values are store in the file appsettings.json.   It is a json based configuration file.  An example file could look something like this.

{
  "NumberOfItemsToShow": 20,
  "Title": "Demo Application",
  "Topics": ["Asp.net","Asp.net core", ".net core", "Xamarin"],
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  }
}


In asp.net you stored your app settings in a file called web.config.  If you wanted different values for different environments you wrote a config transform to change the value for that environment.  In asp.net core you create a appsettings.[environment name].json to overwrite values for the other environment.


To start off we are going to create a class to hold our config values.  I am going to call it SiteSettings.

using System;
using System.Collections.Generic;
namespace SettingsDemo.Models
{
    public class SiteSettings
    {
        public int NumberOfItemsToShow { get; set; }
        public string Title { get; set; }

        public List<string> Topics { get; set; }
    }
}


Now in the file startup.cs we need to update the startup function to get the hosting environment and load the config values.

        private readonly IHostingEnvironment hostingEnvironment;


        public Startup(IHostingEnvironment env, IConfiguration config)
        {
            var builder = new ConfigurationBuilder()
               .SetBasePath(env.ContentRootPath)
               .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
               .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
               .AddEnvironmentVariables(); 
            hostingEnvironment = env;
            Configuration = config;
        }


To load the setting we need to change the Configure Service method.


        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<SiteSettings>(Configuration);
            services.AddMvc();
        }


Finally we need to use dependency injection to get access to them in the controller we need to use them in.


        private SiteSettings siteSettings;

        public HomeController(IOptions<SiteSettings> settings)
        {
            siteSettings = settings.Value;
            
        }


Hope this helps

You can find the sample code on GitHub

https://github.com/vb2ae/AspNetCoreConfiguration

Saturday, April 7, 2018

Visual Studio for Mac MSTests project wont load

I opened a Xamarin Forms project I created on a windows pc with visual studio for Mac.




Besides the UWP version of the app not being able to load the MS Tests unit tests could not be loaded either.  Since I really need the unit tests to run I edited the project so it would work.

This is the original

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{11CF56D3-607A-4251-B491-E88D59114CFE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>App4.test</RootNamespace>
<AssemblyName>App4.test</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.1.2.0\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="UnitTest1.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.1.2.0\build\net45\MSTest.TestAdapter.targets')" />
</Project>

I changed the project to a dot net core unit test project by editing the project file.  Here is what I changed it to.  The updated test project uses MS Test V2


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0-preview-20170628-02" />
    <PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
    <PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\App4\App4\App4.csproj" />
  </ItemGroup>
</Project>