Wydajność: HashSet + struktury

Struktury mogą być doskonałym rozwiązaniem w połączeniu ze słownikiem czy HashSet. Zwykle projektuje je się w sposób immutable, więc stanowią naturalny wybór na klucze w HashSet.

Niestety bardzo łatwo popełnić błąd, który poskutkuje gwałtownym spadkiem wydajności.  Załóżmy, że mamy dwie struktury, jedna zawierająca wyłącznie typy proste (value type), a druga również typ referencyjny czyli string:

    public struct WithValueType
    {
        public int ValueType1;
        public int ValueType2;
    }

    public struct WithReferenceType
    {
        public int ValueType;
        public string RefType;
   
    }

Napiszmy teraz prosty benchmark, sprawdzający ile trwa dodanie obiektów do HashSet:

        int times = 1000000;
        
        var withValueType = new HashSet<WithValueType>();
        var withReferenceType = new HashSet<WithReferenceType>();

        // withValueType
        Stopwatch timer = Stopwatch.StartNew();

        for (int i = 0; i < times; i++)
            withValueType.Add(new WithValueType {ValueType1 = i, ValueType2 = i});

        timer.Stop();
        Console.WriteLine($"WithValueType: {timer.ElapsedMilliseconds}");

        // withReferenceType
        timer = Stopwatch.StartNew();

        for (int i = 0; i < times; i++)
            withReferenceType.Add(new WithReferenceType { ValueType = i, RefType = i.ToString() });

        timer.Stop();
        Console.WriteLine($"withReferenceType: {timer.ElapsedMilliseconds}");
      

Na ekranie zobaczymy następujące wyniki:

1

Skąd ta różnica? To już nie jest mikro-optymalizacja, ale bardzo poważny problem wydajnościowy, który ostatnio zmarnował mi sporo czasu na profilowaniu aplikacji. Wiemy, że struktury możemy porównywać poprzez wartość. Jeśli w kodzie mamy warunek np. if(a != b), wtedy konkretne wartości zostaną porównane, a nie jak w przypadku klas, adres instancji. Problem w tym, że wykorzystywany jest mechanizm reflekcji, który zawsze jest wolniejszy. Z dokumentacji MSDN wiemy, że Equals dla struktur działa następująco:

1. Jeśli struktura zawiera przynajmniej jeden typ referencyjny, wtedy jest używany mechanizm reflekcji.
2. Jeśli struktura zawiera wyłącznie typy proste (value types), wtedy dane porównywane są bajt po bajcie.

Zobaczmy implementację ValueType.Equals:

public override bool Equals (Object obj) { 
            BCLDebug.Perf(false, "ValueType::Equals is not fast.  "+this.GetType().FullName+" should override Equals(Object)"); 
            if (null==obj) {
                return false; 
            }
            RuntimeType thisType = (RuntimeType)this.GetType();
            RuntimeType thatType = (RuntimeType)obj.GetType();
 
            if (thatType!=thisType) {
                return false; 
            } 

            Object thisObj = (Object)this; 
            Object thisResult, thatResult;

            // if there are no GC references in this object we can avoid reflection
            // and do a fast memcmp 
            if (CanCompareBits(this))
                return FastEqualsCheck(thisObj, obj); 
 
            FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 
            for (int i=0; i<thisFields.Length; i++) {
                thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false);
                thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false);
 
                if (thisResult == null) {
                    if (thatResult != null) 
                        return false; 
                }
                else 
                if (!thisResult.Equals(thatResult)) {
                    return false;
                }
            } 

            return true; 
        }

Po metodzie CanCompareBits widzimy, że szybkie sprawdzanie jest wyłącznie możliwe, gdy nie ma typów referencyjnych. Wtedy po prostu są sprawdzane wartości bajtów (szybka operacja). Problemy zaczynają się, gdy dane są przechowywane na stercie, szczególnie w różnych miejscach.

W praktyce, gdy mamy typy referencyjne, wtedy obowiązkowo implementujemy Equals. Powyższe rozważania jednak nie usprawiedliwiają faktu, że to struktura z typami prostymi, a nie referencyjnymi, stanowi największy problem. HashSet zawsze wywołuje GetHashCode. W dokumentacji przeczytamy, że:

        /*=================================GetHashCode==================================
        **Action: Our algorithm for returning the hashcode is a little bit complex.  We look
        **        for the first non-static field and get it's hashcode.  If the type has no
        **        non-static fields, we return the hashcode of the type.  We can't take the 
        **        hashcode of a static member because if that member is of the same type as
        **        the original type, we'll end up in an infinite loop. 
        **Returns: The hashcode for the type. 
        **Arguments: None.
        **Exceptions: None. 
        ==============================================================================*/
        [System.Security.SecuritySafeCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [MethodImplAttribute(MethodImplOptions.InternalCall)] 
        public extern override int GetHashCode();

Rozwiązaniem jest własna implementacja GetHashCode:

    public struct WithValueType
    {
        public int ValueType1;
        public int ValueType2;

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public bool Equals(WithValueType other)
        {
            return ValueType1 == other.ValueType1 && ValueType2 == other.ValueType2;
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (ValueType1*397) ^ ValueType2;
            }
        }
    }

    public struct WithReferenceType
    {
        public int ValueType;
        public string RefType;

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public bool Equals(WithReferenceType other)
        {
            return ValueType == other.ValueType && string.Equals(RefType, other.RefType);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                return (ValueType*397) ^ (RefType != null ? RefType.GetHashCode() : 0);
            }
        }
    }

Wyniki prezentują się teraz dużo lepiej:
2

Warto również zaimplementować interfejs IEquatable, jeśli chcemy uniknąć boxingu. Więcej informacji w moim poprzednim wpisie.

ASP.NET WebAPI – Pakiet Microsoft.Owin.Testing

W poprzednim wpisie pokazałem częściowo pakiet Owin do testowania. Został on użyty do testów dostawcy. Myślę, że zasługuję on na osobny wpis, ponieważ często w testach integracyjnych czy UI jest przydatny.

Owin.Testing to nic innego jak testowy serwer WWW. Zamiast odpalać proces IIS ręcznie w SetUP, można skorzystać z Owin. Stwórzmy zatem projekt dla testów i zainstalujmy kilka pakietów:

1. nUNit
2. Microsoft.Owin.Testing
3. Microsoft.Owin.Host.HttpListener
4. Restsharp

Następnie podstawowy test wygląda następująco:

   [TestFixture]
    public class WebTests
    {
        [Test]
        public void OwinAppTest()
        {
            using (WebApp.Start<Mystartup>("http://localhost:5454"))
            {
                var httpclient = new RestClient("http://localhost:5454");
                var response =  httpclient.Get(new RestRequest("/api/values"));

                string content = response.Content;

                // assert
            }
        }
    }

Widzimy, że musimy stworzyć OWIN startup w aplikacji ASP.NET WebAPI. Tworzymy zatem plik z szablonu:
1

Domyślnie zostanie wygenerowany następująca klasa:

    public class MyStartup
    {
        public void Configuration(IAppBuilder app)
        {
            // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
        }
    }

Musimy umieścić tam kod, który zwykle zostaje wykonany zamiast Global.asax. Oznacza to, że możemy usunąć Global.asax, a następnie umieścić w MyStartup następującą konfigurację:

  public class MyStartup
    {
        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();

            WebApiConfig.Register(config);

            app.UseWebApi(config);

        }
    }

Aby powyższy kod skompilował się, należy najpierw zainstalować w projekcie WebApi pakiety “Microsoft.AspNet.WebApi.OwinSelfHost” oraz “Microsoft.Owin.Host.SystemWeb”. Pierwszy pakiet jest potrzebny, aby mogło się hostować aplikację w procesie, a nie w IIS. Powyższy test, nie będzie odpalać zatem jakiejkolwiek instancji IIS.

Z kolei drugi pakiet (SystemWeb) jest wymagany w celu odpalenia Startup, w momencie hostowania aplikacji w IIS.

Za pomocą Owin, w łatwy sposób można odpalać aplikacje self-host. Analogiczne pakiety można znaleźć m.in. dla Nancy.

CONSUMER DRIVEN CONTRACTS w PACT-NET – implementacja testów dostawcy

W poprzednim wpisie przedstawiłem framework PACT.NET. Zdołaliśmy zaimplementować testy po stronie konsumentów. Wynikiem tego, był wygenerowany plik JSON zawierający nagrane testy. Dla przypomnienia, test po stronie konsumenta wyglądał następująco:

[TestFixture]
public class ConsumerTests
{
    private IMockProviderService _mockProviderService;
    private IPactBuilder _pactProvider;
 
    [OneTimeSetUp]
    public void OneTimeSetUp()
    {
        _pactProvider = new PactBuilder()
            .ServiceConsumer("ConsumerName")
            .HasPactWith("ProviderName");
 
        _mockProviderService = _pactProvider.MockService(3423,false);
    }
 
    [OneTimeTearDown]
    public void OneTimeTearDown()
    {
        _pactProvider.Build();
    }
 
    [Test]
    public void Should_Return_FirstName()
    {
        //Arrange
        _mockProviderService.Given("there are some persons data")
            .UponReceiving("a request to retrieve all persons")
            .With(new ProviderServiceRequest
            {
                Method = HttpVerb.Get,
                Path = "/api/persons"     
            })
            .WillRespondWith(new ProviderServiceResponse
            {
                Status = 200,
                Headers = new Dictionary<string, string>
                {
                    { "Content-Type", "application/json; charset=utf-8" }
                },
                Body = new[]
                {
                    new
                    {
                        FirstName = "Piotr",
                    }
                }
            });
 
        var consumer = new PersonsApiClient("http://localhost:3423");
 
        //Act
        var persons = consumer.GetAllPersons();
 
        //Assert
        CollectionAssert.IsNotEmpty(persons);
        CollectionAssert.AllItemsAreNotNull(persons.Select(x=>x.FirstName));
 
        _mockProviderService.VerifyInteractions();
    }
}

Z kolei wygenerowany JSON:

{
  "provider": {
    "name": "ProviderName"
  },
  "consumer": {
    "name": "ConsumerName"
  },
  "interactions": [
    {
      "description": "a request to retrieve all persons",
      "provider_state": "there are some persons data",
      "request": {
        "method": "get",
        "path": "/api/persons"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": [
          {
            "FirstName": "Piotr"
          }
        ]
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.1.0"
  }
}

Przejdźmy zatem do testu po stronie dostawcy:

    [TestFixture]
    public class ProducerTests
    {
        [Test]
        public void Ensure_That_Consumer_Requirements_Are_Met()
        {
            var config = new PactVerifierConfig();
            IPactVerifier pactVerifier = new PactVerifier(() => { }, () => { }, config);

            pactVerifier
                .ProviderState(
                    "there are some persons data",
                    setUp: DataSetup,tearDown:DataTearDown);
       
            using (var testServer = TestServer.Create<Startup>())
            {
                pactVerifier
                   .ServiceProvider("Persons API", testServer.HttpClient)
                   .HonoursPactWith("Consumer")
                   .PactUri(@"consumername-providername.json")
                   .Verify();
            }
        }

        private void DataTearDown()
        {
        }


        private void DataSetup()
        {
        }

    }

W celu uruchomienia serwera, używamy Microsoft.Owin.Testing czyli klasy TestServer. W momencie odpalenia testu, zostanie zatem stworzony serwer z naszą aplikacją. Następnie “odgrywany” będzie test wygenerowany przez konsumenta. Operujemy na prawdziwych danych, a nie na mock’ach, stąd metody DataSetup oraz DataTearDown w praktyce zawierają odpowiedni kod, dodający oraz usuwający dane testowe.

Po wykonaniu testu, zostanie wygenerowany plik persons_api_verifier.log:

2016-06-01 19:54:01.894 +01:00 [Debug] Verifying a Pact between ConsumerName and ProviderName
  Given there are some persons data
    a request to retrieve all persons
      with GET /api/persons
        returns a response which
          has status code 200
          includes headers
            'Content-Type' with value application/json; charset=utf-8
          has a matching body (FAILED - 1)

Failures:

1) Expected: [
  {
    "FirstName": "Piotr"
  }
], Actual: [
  {
    "FirstName": "Piotr",
    "LastName": "Zielinski"
  },
  {
    "FirstName": "first name",
    "LastName": "last name"
  }
]

Widzimy, że test zakończył się niepowodzeniem, ponieważ oczekiwana zawartość Content jest inna niż ta zwrócona przez prawdziwy serwis.

Za pomocą PathVerifier definiujemy, które testy chcemy wykonać. W naszym przypadku jest tylko jeden. W praktyce definiuje się wiele stanów i kilka plików JSON.

Consumer driven contracts – pact-net

O Consumer-Driven-Contracts pisałem już tutaj. Sam koncept jest dosyć prosty, ale bez odpowiednich narzędzi może być mozolny w wdrożeniu.
Pack-net, jak nie trudno się domyślić jest implementacją biblioteki pact w C#. Kod źródłowy możną znaleźć na GitHub.

Wiemy, aby zaimplementować Consumer-Driven-Contracts (CDC) musimy napisać testy zarówno po stronie konsumenta jak i dostawcy. Zwykle, konsumenci nie testują prawdziwej usługi, a jedynie operują na mock’ach. Następnie dostawca, wykona wszystkie testy konsumenckie na prawdziwej usłudzę. Sztuczka polega na tym, aby w jakiś sposób przekazać do dostawcy testy zdefiniowane przez konsumenta. Nie chcemy generować biblioteki DLL i jej przekazywać. Pact wygeneruje za nas plik JSON, ze wszystkimi testami. Podsumowując za pomocą Pact:

1. Konsumenci definiują klasyczne testy jednostkowe. Wykonywane są one w izolacji na mock’u usługi.
2. Testy jednostkowe są “nagrywane” i  zapisywane w pliku JSON.
3. Plik (lub pliki w przypadku wielu konsumentów) JSON  są odczytywane przez producenta. Nagrane testy są odtwarzane, ale będą już wykonywane na prawdziwej usłudze.

Jeśli tworzymy usługę, wtedy krok trzeci gwarantuje nam, że spełniamy wszystkie wymogi konsumentów. Jeśli konsument A potrzebuje np. pole FirstName w odpowiedzi, wtedy zdefiniuje to za pomocą testu jednostkowego, a dostawca następnie zweryfikuje to.
Pact dostarcza łatwy interfejs i nie musimy się martwić np. odpaleniem serwer’a web w testach dostawcy, które zawsze wykonywane są na prawdziwej usłudze. Podobnie, dzięki Pact łatwo tworzyć mock’a usługi. Po prostu wystarczy zdefiniować oczekiwane nagłówki czy zawartość ciała.

Myślę, że najłatwiej będzie to zrozumieć na przykładzie. Załóżmy, że tworzymy usługę zwracającą listę osób. Nasza solucja zatem będzie składać się z:
1. Provider.Web – usługa
2. Provider.Web.Tests – testy dostawcy (wykonywane na prawdziwej usłudze)
3. Consumer – prosty klient łączący się z usługą
4. Consumer.Tests – testy konsumenckie (wykonywane na mock’u)

W dzisiejszym wpisie zajmiemy się punktami 1,3 oraz 4, a następnym razem zajmiemy się testami dostawcy.

Zacznijmy zatem od dostawcy (punkt 1):

    public class PersonsController : ApiController
    {
        public PersonInfo[] GetAll()
        {
            var person1 = new PersonInfo {FirstName = "Piotr", LastName = "Zielinski"};
            var person2 = new PersonInfo { FirstName = "first name", LastName = "last name" };

            return new PersonInfo[] {person1, person2};
        }
    }

    public class PersonInfo
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

Zwykłe WebApi, którego odpowiedź wygląda następująco:

<ArrayOfPersonInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Provider.Web.Controllers">
<PersonInfo>
<FirstName>Piotr</FirstName>
<LastName>Zielinski</LastName>
</PersonInfo>
<PersonInfo>
<FirstName>first name</FirstName>
<LastName>last name</LastName>
</PersonInfo>
</ArrayOfPersonInfo>

Następnie zdefiniujemy klienta (konsumenta – punkt 3):

   public class PersonsApiClient
    {
        public string BaseUri { get; set; }

        public PersonsApiClient(string baseUri)
        {
            BaseUri = baseUri;
        }

        public PersonInfo[] GetAllPersons()
        {
            var client = new RestClient(BaseUri);
            var persons = client.Get<List<PersonInfo>>(new RestRequest("api/persons")).Data;

            return persons.ToArray();
        }
    }

Ostatni etap w dzisiejszym wpisie to zdefiniowanie testu konsumenta (punkt 4):

    [TestFixture]
    public class ConsumerTests
    {
        private IMockProviderService _mockProviderService;
        private IPactBuilder _pactProvider;

        [OneTimeSetUp]
        public void OneTimeSetUp()
        {
            _pactProvider = new PactBuilder()
                .ServiceConsumer("ConsumerName")
                .HasPactWith("ProviderName");

            _mockProviderService = _pactProvider.MockService(3423,false);
        }

        [OneTimeTearDown]
        public void OneTimeTearDown()
        {
            _pactProvider.Build();
        }

        [Test]
        public void Should_Return_FirstName()
        {
            //Arrange
            _mockProviderService.Given("there are some persons data")
                .UponReceiving("a request to retrieve all persons")
                .With(new ProviderServiceRequest
                {
                    Method = HttpVerb.Get,
                    Path = "/api/persons"      
                })
                .WillRespondWith(new ProviderServiceResponse
                {
                    Status = 200,
                    Headers = new Dictionary<string, string>
                    {
                        { "Content-Type", "application/json; charset=utf-8" }
                    },
                    Body = new[]
                    {
                        new
                        {
                            FirstName = "Piotr",
                        }
                    }
                });

            var consumer = new PersonsApiClient("http://localhost:3423");

            //Act
            var persons = consumer.GetAllPersons();

            //Assert
            CollectionAssert.IsNotEmpty(persons);
            CollectionAssert.AllItemsAreNotNull(persons.Select(x=>x.FirstName));

            _mockProviderService.VerifyInteractions();
        }
    }

Za pomocą klasy PactBuilder budujemy mock usługi czyli IMockProviderService. Następnie definiujemy wejścia i wyjścia mocka. Widzimy, że jeśli zapytanie GET przyjdzie na /api/persons wtedy zwracamy jeden obiekt, zawierający imię. Za pomocą MockProvider możemy zasymulować dowolne zachowanie usługi. W naszym przypadku jest to po prostu GET i odpowiedź. W bardziej zaawansowanych przypadkach dochodzą do tego parametry query, nagłówki, metody POST, DELETE itp.

Mock zwróci zatem tylko jeden obiekt, zawierający imię Za pomocą następnych asercji, zweryfikujemy, że faktycznie imię musi zawierać jakąś treść. Jeśli pole FirstName na tym etapie jest puste, oznacza to, że implementacja klienta jest błędną – np. deserialziacja nie działa tak jak należy. Widzimy, że port 3423 został dobrany losowo, ponieważ nie ma to znaczenia dla mock’a.
VerifyInteractions sprawdzi również tzw. interakcje. W powyższym przypadku oznacza to, że dokładnie jedno zapytanie GET “api/persons” zostało wysłane do usługi. Jeśli nasz klient zawierałby błąd, np. dwukrotne wysłanie zapytania, wtedy test również zakończyłby się błędem.

Po wykonaniu testu, zostanie on nagrany i zapisany w pliku “consumername-providername.json”:

{
  "provider": {
    "name": "ProviderName"
  },
  "consumer": {
    "name": "ConsumerName"
  },
  "interactions": [
    {
      "description": "a request to retrieve all persons",
      "provider_state": "there are some persons data",
      "request": {
        "method": "get",
        "path": "/api/persons"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json; charset=utf-8"
        },
        "body": [
          {
            "FirstName": "Piotr"
          }
        ]
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.1.0"
  }
}

W kolejnym wpisie zajmiemy się implementacją testów dostawcy, które będą wykorzystywać właśnie wygenerowany plik JSON, w celu wykonania takiego samego testu na prawdziwej usłudze.

DbUp – generowanie silnie typowanych nazw procedur za pomocą T4

O bibliotece DbUp pisałem tutaj:
DBUP – AKTUALIZACJA BAZ DANYCH

Z kolei o szablonach T4  stworzyłem cały mini-cykl, który można znaleźć tutaj.

Problem z DbUp jest taki, że wywołuje on po prostu kolejne skrypty. Następnie sami za pomocą np. Dapper, musimy podać nazwę procedury czy tabeli na której chcemy operować. DbUp + Dapper to częsta kombinacja narzędzi. Jeśli korzystamy z rozbudowanego ORM, takiego jak EntitityFramework, wtedy wszystkie tabele czy nazwy procedur i tak są silnie typowane ponieważ framework je wygeneruje. Za pomocą  EF Migrations, można uzyskać to samo co robi DbUp.

Jeśli jednak korzystamy tylko z Dapper+DbUp, wciąż możemy mieć silnie typowane nazw. Załóżmy że w projekcie DbUp mamy następującą strukturę skryptów:

-> StoredProcs
—>1. GetFirstNameById
—>2. GetData
—>3. UpdateData

->Functions:
—>1. Split
—>2. MaxData

->Types
—>1.  CustomType1
—>2.  CustomType2

Wtedy można pokusić się o następujący szablon T4:

<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #>
<#@ import namespace="System.IO" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #>
namespace Data.Sql
{
<#
	 string[] foldersToGenerate = new [] { "StoreProcs", "Functions","Types" };
	 string scriptsPath = Host.ResolvePath(@"..\Database\Scripts");

	 foreach(var folderName in foldersToGenerate)
	 {
	     WriteLine(string.Format("\tinternal static class {0}\n\t{{",folderName));

		 foreach(var directory in System.IO.Directory.GetDirectories(scriptsPath, "*" + folderName, SearchOption.AllDirectories))
		 {
			foreach(var fileName in Directory.GetFiles(directory))
			{
				string name = Path.GetFileNameWithoutExtension(fileName).Split(' ').Last();
				string declaration = string.Format("\t\tinternal const string {0} = \"{0}\";", name);

				WriteLine(declaration);
			}
		 }

		 WriteLine("\t}");
	 }
#>
}

Szablon przejdzie automatycznie po wszystkich plikach i wygeneruje statyczną klasę zawierającą stałe, np.:

internal static class StoreProcs
{
    internal const string GetFirstNameById="GetFirstNameById";
    internal const string GetData="GetData";
    internal const string UpdateData="UpdateData";
}

Rozwiązanie zakłada, że nazwa pliku pokrywą się z nazwą zasobu (czyli procedury, funkcji czy typu). Analogicznie sprawa wygląda z tabelami. Myślę, że jest to lepsze rozwiązanie niż hardcodowanie za każdym razem procedury.

ASP.NET Core RC2 – mapowanie pliku konfiguracyjnego na POCO

Często tworzymy pewien “wrapper” dla wartości pochodzących z Web.Config, np.:

class Config
{
    public int MaxResults{get;set;}
}

Następnie np. w IoC inicjalizujemy obiekt wartościami pochodzącymi z Web.Config:

Config config = new Config();
config.MaxResults = ConfigutationManager.AppSettings["MaxResults].Value;

W ASP.NET Core istnieje łatwy sposób mapowania wartości bezpośrednio na klasę w C#. Oczywiście coś podobnego było i tak możliwe w starych wersjach ASP.NET, ale nie było to tak wygodne.

Załóżmy, że mamy następujący plik konfiguracyjny w ASP.NET Core (plik settings.json):

{
"ApplicationInsights": {
"InstrumentationKey": ""
},
"Config": {
"MaxResults": 15
},

"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

Przede wszystkim musimy zainstalować pakiet “Microsoft.Extensions.Options.ConfigurationExtensions”, np. poprzez dodanie odpowiedniego wpisu w project.json:

"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0-rc2-final"

W Start.ConfigureService dodajemy następujący kod:

            services.AddOptions();
            services.Configure<Config>(Configuration.GetSection("Config"));

Pierwsza linia umożliwi m.in. wstrzyknięcie Config do kontrolera. Druga z kolei, naturalnie czyta wartości z pliku JSON. Samo wstrzyknięcie sprowadza się do zdefiniowania odpowiedniego konstruktora:

        private readonly IOptions<Config> _configValue;

        public HomeController(IOptions<Config> _configValue )
        {
            this._configValue = _configValue;
        }

         public IActionResult About()
        {
            ViewData["Message"] = _configValue.Value.MaxResults.ToString();

            return View();
        }

Dosyć zwięzłe rozwiązanie. Nie trzeba samemu wstrzykiwać do kontenera IoC czy nawet odczytywać konkretnych wartości z pliku konfiguracyjnego.

.NET Core oraz ASP.NET Core

.NET Core to nowy framework od Microsoft’u, który aktualnie wciąż jest w produkcji. Zdecydowałem się napisać dzisiejszy post, ponieważ w ostatnim czasie nastąpiło wiele zmian w wersjonowaniu i nazewnictwie .NET. Moim zdaniem wprowadziło to trochę zamieszania.

Przede wszystkim, co to jest .NET Core? To nowa wersja framework’a, która z założenia ma być wieloplatformowa oraz open-source. To wielka zmiana ze strony Microsoft, ale już od kilku lat można obserwować, że Microsoft zmierzą w kierunku, który w latach 90 był kompletnie nie do pomyślenia. .NET Core będzie zatem działać zarówno na Windows jak i Mac czy Linux.

Druga różnica to fakt, że .NET Core to po prostu pakiet NuGet. Doskonale nadaje się to do aplikacji webowych. Dzisiaj, serwer web musi mieć zainstalowany .NET Framework. Oznacza to, że ciężko mieć kilka aplikacji ASP.NET uruchomianych w różnych wersjach .NET Framework.  Dzisiejsza wersja .NET Framework jest bardzo mocno powiązana z systemem operacyjnym i z punktu architektonicznego stanowi monolit.

Wchodząc trochę w szczegóły techniczne, .NET Core składa się z podstawowej biblioteki klas (CoreFx), oraz środowiska uruchomieniowego CoreClr. Na GitHub można śledzić postęp prac:

https://github.com/dotnet/corefx

https://github.com/dotnet/coreCLR

Ktoś może zapytać się, ale co z Mono? Aktualnie jest to implementacja .NET na Linuxa. Po wprowadzeniu .NET Core nie jest jeszcze jasne co stanie się z Mono, ponieważ jest to niezależny framework. Na dzień dzisiejszy, wydaje mi się, że w pewnym czasie .NET Core zastąpi całkowicie Mono.

Wprowadzenie .NET Core nie oznacza, że nagle będziemy mogli uruchamiać wszystkie aplikacje na Linux. .NET Core to jedynie podzbiór .NET Framework. Wiadomo, że główną motywacją były aplikacje webowe czyli ASP.NET. Inne typy aplikacji typu WPF nie będą oczywiście dostępne (przynajmniej bazując na informacjach, które są już dostępne).

Nie trudno teraz domyślić się, że ASP.NET Core to nowa wersja ASP.NET,  napisana pod .NET Core. Oznacza to, że będzie mogłaby być hostowana na Linux. Analogicznie do .NET Core, ASP.NET Core jest również open-source.

ASP.NET 5, o którym pisałem wiele razy na blogu, został zmieniony na ASP.NET Core. Informacje, które wcześniej podawałem,  dotyczą zatem ASP.NET Core, a nie ASP.NET 5, którego nazwa została zmieniona.  Jedną z większych zmian ASP.NET Core to wprowadzenie projects.json, zamiast packages.config. Więcej szczegółów można znaleźć w poprzednich wpisach.

Podsumowując, .NET Core oraz ASP.NET Core dadzą nam:

  • Wieloplatformowość
  • Modularność i niezależność od .NET Framework zainstalowanego na systemie – każda aplikacja, będzie mogą korzystać z innej wersji framework’a.
  • Open-source

Warto również przejrzyj się następującemu diagramowi (źródło  http://www.hanselman.com):

Jak widzimy, ASP.NET Core 1.0, będzie działać również na starym, monolitowym frameworku (.NET Framework 4.6). Taka konfiguracja też jest możliwa tzn. ASP.NET Core  + .NET Framework, ale w celu uzyskania wieloplatformowości należy zainteresować się ASP.NET Core 1.0 + .NET Core.

Visual Studio 15 Preview: Wykonywanie kodu w oknie Interactive.

O oknie “Interactive” pisałem już tutaj. Bardzo pożyteczna funkcja, w moim przypadku zastępująca LinqPad.

W wersji 15 Preview (którą można pobrać z stąd), dodano możliwość wykonywania zaznaczonego kodu. Załóżmy, że funkcja Main wygląda następująco:

1

Możliwe jest teraz zaznaczenie kodu i wykonanie go w oknie Interactive C#:

2

Warto zwrócić uwagę również na skrót – Ctrl+E. To bardzo pożyteczne, gdy chcemy szybo coś przetestować. Okno otworzy się automatycznie i zostanie wykonany zaznaczony kod:

3

Niby nic wielkiego, ale wiele razy musiałem otwierać LinqPad, albo w przypadku wersji 2015 ręcznie okno Interactive.

c# 7.0 – operator Is, wzorce rekurencyjne

Dzisiaj przyszedł czas na wzorzec agregujący w pewien sposób wszystkie poprzednie, a mianowicie wzorzec rekurencyjny. Załóżmy, że mamy następującą hierarchię klas, np.:

    public class Employee
    {
        public string Name { get; set; }
        public object Salary { get; set; }
    }

    public class Manager : Employee
    {
        public object DirectReports { get; set; }
    }

W poprzednich wersjach C#, było możliwe jedynie sprawdzenie czy obiekt jest typu “Manager” czy innej implementacji. W wersji 7.0 możemy:

            object employee = new Manager()
            {
                Salary = 53,
                DirectReports = new Employee[5]
            };

            if (employee is Manager { Salary is int salary,DirectReports is Employee[] directReports})
            {
                Console.WriteLine("It's a manager with salary {0} and direct reports {1}", salary, directReports.Length);
            }

Widzimy tutaj wzorzec rekurencyjny składający się z:

  • type pattern – sprawdzamy czy obiekt jest typu Manager
  • zagnieżdżonego type pattern – sprawdzamy czy Salary jest liczbą całkowitą
  • zagnieżdżonego type pattern – sprawdzamy czy DirectReports jest tablicą.

Jeśli któryś z warunków jest nieprawdziwy, całość zwróci false. Jeśli np. zamienimy tablicę na kolekcję, wtedy na ekranie nic się nie wyświetli:

        private static void Main(string[] args)
        {
            object employee = new Manager()
            {
                Salary = 53,
                DirectReports = new List<Employee>()
            };

            if (employee is Manager { Salary is int salary,DirectReports is Employee[] directReports})
            {
                Console.WriteLine("It's manager with salary {0} and direct reports {1}", salary, directReports.Length);
            }
        }

Można również umieścić “constant pattern”, o którym pisałem w poprzednim poście:

            object employee = new Manager()
            {
                Name="Piotr"
            };

            if (employee is Manager { Name is "Piotr"})
            {
                Console.WriteLine("Name: Piotr");
            }

W tym przypadku, instrukcja zwróci true, gdy właściwość Name ma wartość “Piotr”. Constant pattern, nie wyodrębnia zawartości, jedynie sprawdza czy zmienna jest równa danej stałej.

Zmieńmy teraz trochę strukturę klas na:

    public class Employee
    {
        public string Name { get; set; }
    }

    public class Manager : Employee
    {
        public object DirectReport { get; set; }
    }

Za pomocą wzorca możemy rekurencyjnie sprawdzać właściwości o dowolnym zagnieżdżeniu, tzn.:

            object employee = new Manager()
            {
                DirectReport = new Employee { Name = "Piotr" }
            };

            if (employee is Manager { DirectReport is Employee { Name is string name}})
            {
                Console.WriteLine(name);
            }

Rekurencyjna jest zatem przerywana, gdy jakiś warunek zwraca false. Jeśli zwraca true, wtedy kolejne warunki będą sprawdzane, przeglądając graf obiektów o dowolnej głębokości. W powyższym przykładzie, zatem musimy mieć obiekt typu Manager, w którym DirectReport jest typu Employee, a właściwość Name jest napisem.

Tak jak zwykle, na zakończenie zobaczmy kod po kompilacji:

		private static void Main(string[] args)
		{
			object employee = new Manager
			{
				Salary = 53,
				DirectReports = new List&lt;Employee&gt;()
			};
			Manager manager = employee as Manager;
			int salary;
			Employee[] directReports;
			bool arg_65_0;
			if (manager != null)
			{
				int? num = manager.Salary as int?;
				salary = num.GetValueOrDefault();
				if (num.HasValue)
				{
					directReports = (manager.DirectReports as Employee[]);
					arg_65_0 = (directReports != null);
					goto IL_65;
				}
			}
			arg_65_0 = false;
			IL_65:
			bool flag = arg_65_0;
			if (flag)
			{
				Console.WriteLine("It's manager with salary {0} and direct reports {1}", salary, directReports.Length);
			}
		}

Wyraźnie widzimy, że wszystkie warunki muszą zostać spełnione tzn.:

  • Employee musi być typu Manager
  • Salary jest liczbą całkowitą
  • DirectReports jest tablicą Employee

Ponadto, jak widzimy z powyższego kodu, typy proste mogą być nullable, o ile mają w sobie jakąś wartość. Zatem salary mogłaby być typu int?, o ile wartość nie jest NULL.

c# 7.0 – operator Is, część druga

Ostatnio przyjrzeliśmy się pierwszemu usprawnieniu operatora IS. Tak naprawdę jest to część dużo większej zmiany, a mianowicie dopasowywania wzorców (pattern matching). Celem jest filtracja i wyodrębnianie konkretnych danych z obiektu. Dla osób znających programowanie funkcyjne,  nie stanowi to prawdopodobnie wielkiej zmiany. Język C# zawiera coraz więcej elementów programowania funkcyjnego,  przy tym samym zachowując swój styl zaczerpnięty  z CPP i klasycznych języków obiektowych.

W ostatnim poście opisałem tzw. “type pattern” ponieważ wydobywaliśmy dane na podstawie typu. Operator zwracał “true”, gdy typ był zgodny z naszymi oczekiwaniami:

object test = 5;
 
if(test is int integerValue)
{
    Console.WriteLine(integerValue.GetType());
    Console.WriteLine(integerValue * 5);
}

Kolejnym rodzajem wzorca jest “var pattern”:

            object test = 5;

            if (test is var x)
            {
                Console.WriteLine(x.GetType());
            }

Wzorzec “var” zwraca zawsze true. Oznacza to, że instrukcja IF zawsze wykona się. Na ekranie w powyższym przypadku wyświetli się “System.Int32”. Zmienna x jednak jest typu System.Object, zatem nie możemy wykonać na niej bezpośrednio operacji arytmetycznych. Po kompilacji, powyższa konstrukcja wygląda następująco:

                        object test = 5;
			object x = test;
			bool flag = true;
			if (flag)
			{
				Console.WriteLine(x.GetType());
			}

Wiem, że na razie wygląda to bezużytecznie, ale w przyszłym poście zajmiemy się rekurencyjnymi wzorcami i wtedy znajduje to zastosowanie.

Kolejny wzorzec to “wildcard pattern”:

             object test = 5;

            if (test is *)
            {
                Console.WriteLine(test.GetType());
            }

Na ekranie oczywiście znów wyświetli się System.Int32. Analogicznie jak wzorzec var, zawsze zwraca true. Jedyna różnica to fakt, że w przypadku wildcard, nie wydobywamy żadnych danych. Po dekompilacji zobaczymy po prostu:

object test = 5;
Console.WriteLine(test.GetType());

Następny wzorzec to “const pattern”, czyli dopasowywanie stałych. Przydaje się np. w instrukcji switch. Przykład:

            object test = 3;

            switch (test)
            {
                case int integerValue when integerValue <= 5:
                    Console.WriteLine(integerValue );
                    break;

                case int integerValue:
                    Console.WriteLine(integerValue * 5);
                    break;
                case string text:
                    Console.WriteLine(text.ToLower());
                    break;
                case *:
                    Console.WriteLine("All");
                    break;
            }

W powyższym przykładzie, dopasowywanie zostało umieszczone w instrukcjach warunkowych CASE. Normalnie, switch porównuje dane za pomocą operatora ==. W c# 7.0 wspiera on dopasowywanie wzorców i w powyższym kodzie skorzystaliśmy ze wzorca “Type” oraz “wildcard”. Ponadto dodano klauzule “when”, gdzie możemy filtrować dodatkowo wartości. Powyższy kod, pokazuje również, że w pewnych sytuacjach, zarówno “wildcard” jak i “var pattern” są przydatne. Oczywiście nie korzystamy z nich jak mamy pojedynczą instrukcję warunkową, ale w przypadku switch jest już to realny przykład użycia.

Po dekompilacji otrzymamy:

	internal class Program
	{
		private static void Main(string[] args)
		{
			object test = 3;
			object obj = test;
			int? num = obj as int?;
			int integerValue = num.GetValueOrDefault();
			if (num.HasValue && integerValue <= 5)
			{
				Console.WriteLine(integerValue);
			}
			else
			{
				num = (obj as int?);
				int integerValue2 = num.GetValueOrDefault();
				if (num.HasValue)
				{
					Console.WriteLine(integerValue2 * 5);
				}
				else
				{
					string text = obj as string;
					if (text != null)
					{
						Console.WriteLine(text.ToLower());
					}
					else
					{
						Console.WriteLine("All");
					}
				}
			}
		}
	}

W przyszłym poście przyjrzymy się rekurencyjnemu dopasowywaniu, które stanowi agregację przedstawionych wzorców.