Browsed by
Category: Bez kategorii

Storing filters Kendo Grid

Storing filters Kendo Grid

KendoUI Grid control is very flexible and powerful widget to present table data. It provides us a set of useful features. All of them is described very well in Kendo Grid documentation. However there is some cases that these possibilities is not enough to fulfill our requirements.

KendoUI Grid support filters and sorting data in user interface. These filters can do some complex logic. Especially if we decide to pass them to API layer and evaluate server side. Let’s imagine that user set a complex filtering and sorting rules that helps him to manage his data. Then he move to another page and when he want to get back he have to set all filters again.

Persist state functionality

One solution for this problem is persist state mechanism provided to us by KendoUI. It gives a functionality of saving and loading Grid state (filtering, sorting and all information about columns). However all information saved by this mechanism is quite large, so we should use a localStorage to save them.

With this option we can add user a functionality to save and load filters produced by him. Also he can give names to this predefined setups and so on.

But what if we want to do this automatically. To restore filters when user get back to previous page. We can also use this mechanism but it definitely fail if user opens a multiple tabs in web browser and set a different filters on each of them.

Storing settings in query string

To solve this problem we have to write some custom implementation, because there is no that option in Kendo. We decide to store that kind of information in query string because it is a place contextually belonging to single page. We can modify query string when filters changes and restore then on page load.

In following Gist I present a sample code how we can do this. This will be done using Angular framework, but can be also simply adapted to any popular SPA framework.

This service provides us 2 methods: addFilterStoring, which is responsible for adding and restoring query parameters

kendoStateStore.addFilterStoring({
    dataSource: {
        type: "odata",
        transport: {
            read: "//demos.telerik.com/kendo-ui/service/Northwind.svc/Customers"
        }
    },
    height: 550,
    columns: [{
        field: "ContactName",
        title: "Contact Name",
        width: 240
    }, {
        field: "ContactTitle",
        title: "Contact Title"
    }, {
        field: "CompanyName",
        title: "Company Name"
    }, {
        field: "Country",
        width: 150
    }]
}}

Other function provided by our service is a rememberLocationParametersIn. It enables to execute some code e.g. change Grid filtering with remember of query parameters. We can use it as follow:

kendoStateStore.rememberLocationParametersIn(function() {
    // change filters 
    // grid.dataSource.filter(filtersUpdated);
});

This little snippet gives me a missing functionality to powerful Kendo Grid control. I hope that it can help you also.

Image source

Mocking EntityFramework context – Code First

Mocking EntityFramework context – Code First


Source

When I first try to write test for class that use directly, I found that I can’t mock database context directly using for example Moq library. That’s because this class properties isn’t virtual. After some research I found that EF6 could be set up to enable mocking [1,2]. However that is not quite what I need. I had some specific requirements:

  1. I want to trace operations into database tables
  2. It must work for Code First EF6 configuration
  3. It should be generic solution for each database table

Let’s try to satisfy first requirement. We need some method to store all data our mocked object. I decided to write in-memory DbSet object like in [2] link.

public class FakeDbSetKeyed<T, TKey> : DbSet<T>, IQueryable, IEnumerable<T>
    where T : class, IKey<TKey>
{
    Dictionary<TKey, T> _data;
    IQueryable _query;
    public FakeDbSetKeyed()
    {
        _data = new Dictionary<TKey, T>();
        _query = _data.Values.AsQueryable();
    }

    public override T Find(params object[] keyValues)
    {
        return _data[(TKey)keyValues.First()];
    }

    public override T Add(T item)
    {
        _data.Add(item.Id, item);
        return item;
    }

    public override T Remove(T item)
    {
        _data.Remove(item.Id);
        return item;
    }

    public override T Attach(T item)
    {
        if (_data.ContainsKey(item.Id))
        {
            _data[item.Id] = item;
        }
        else
        {
            _data.Add(item.Id, item);
        }
        return item;
    }

    public T Detach(T item)
    {
        _data.Remove(item.Id);
        return item;
    }

    public override T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public override TDerivedEntity Create<TDerivedEntity>()// where TDerivedEntity : class, T
    {
        return Activator.CreateInstance<TDerivedEntity>();
    }

    public override ObservableCollection<T> Local
    {
        get { return new ObservableCollection<T>(_data.Values); }
    }

    Type IQueryable.ElementType
    {
        get { return _query.ElementType; }
    }

    System.Linq.Expressions.Expression IQueryable.Expression
    {
        get { return _query.Expression; }
    }

    IQueryProvider IQueryable.Provider
    {
        get { return _query.Provider; }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        return _data.Values.GetEnumerator();
    }

    public void Clear()
    {
        _data.Clear();
    }
}

You can see two addition to original class definition from MSDN. I added also one additional method (Clear) for purpose of easier operate data sets during testing and also I added generic parameter TKey to specify key type. That enables me to write Find method.

Next, we consider second point of requirement – Code First compatibility. In mentioned MSDN articles we can set up mock for DbContext, but for Code First approach there isn’t work. That’s because during the creation of Mock parameterless constructor is executed. Thus the database could be created each time when we start single test and it is not acceptable and very time consuming. To solve it, I must create fake DbContext.

Let’s suppose that we have following DbContext

public class ItemsContext : DbContext
{
    public ItemsContext()
        : base("DatabaseName")
    {
    }

    public virtual DbSet<User> Users { get; set; }

    public virtual DbSet<Item> Items { get; set; }
}

This context isn’t ready to support its different implementation yet. So I defined interface IItemsContext with sets defined in main context class. I also implement IItemsContext in ItemsContext and change application to use interface beside of specific class.

public interface IItemsContext
{
    DbSet<User> Users { get; set; }
    DbSet<Item> Items { get; set; }
}

public class ItemsContext : DbContext, IItemsContext
{
    public ItemsContext()
        : base("DatabaseName")
    {
    }

    public virtual DbSet<User> Users { get; set; }
    public virtual DbSet<Item> Items { get; set; }
}

Thanks to this modifications in program structure I can write test initializations just like this:

[TestInitialize]
public virtual void TestInitialize()
{
    _dataSet = new FakeDbSetKeyed<TEntity, TKey>();
    var mockContext = new Mock<IBwEntities>(MockBehavior.Strict
    mockContext.Setup(c => c.Set<TEntity>()).Returns(_dataSet);
    _dataSet.Clear();
}

With this set up we fill _dataSet and operate on DbContext (ItemsContext for us) in tests in order to check _dataSet for the results of tested actions.

[1] https://msdn.microsoft.com/en-us/data/dn314429.aspx
[2] https://msdn.microsoft.com/en-us/data/dn314431.aspx

OWASP AppSensor .NET – architektura

OWASP AppSensor .NET – architektura


Źródło

Po dokładnym opisaniu mechanizmu czas na przedstawienie planowanego rozwiązania AppSensor.NET – architektura jest jedną z istotniejszych składowych projektu. Warto też zdefiniować jego cel oraz zestaw kroków, które zawiodą mnie do jego wykonania.

Założenia i funkcjonalności MVP

Zacznijmy więc od zdefiniowania funkcjonalności, które chcemy osiągnąć w projekcie. Oczywiście można założyć, że docelowo chcielibyśmy zaimplementować spójny mechanizm OWASP AppSensor w pełnym jego zakresie, z implementacją wszystkich zdefiniowanych punktów detekcji i integracji z innymi systemami. Jednak z doświadczenia wiem już, że ten sposób określania wymagań nie jest dobry, bo dość słabo motywuje do pracy i do skończenia projektu. Zamiast tego zdefiniujemy MVP naszego projektu.

MVP (Minimum Viable Product) jest to pojęcie zaczerpnięte z metodologii Lean Management (szczupłego zarządzania). Według tej koncepcji pierwsza wersja projektu powinna być jak najmniejsza i udostępniać tylko najważniejsze funkcjonalności. W szczególności jedną funkcjonalność. Dzięki temu można stosunkowo szybko stworzyć projekt, który będziemy dalej rozwijać. Możliwość szybkiego zbudowania czegoś działającego daje sporo motywacji i zapału do tworzenia. Dodatkowo po powstaniu MVP możemy zdecydować, w którą stronę chcemy rozwijać dalej nasz projekt.

Tak więc lista funkcjonalności MVP:

  • bazowy silnik do przechowywania logów (przechowywanie w bazie danych),
  • biblioteka do konfiguracji zasad wykrywania i progów (konfiguracja z kodu),
  • implementacja 3 najpopularniejszych puktów detekcji:
    • RE1: Unexpected HTTP Command
    • RE3: GET When Expecting POST
    • RE4: POST When Expecting GET
  • implementacja 3 metod reakcji:
    • logowanie informacji
    • wylogowanie
    • blokowanie użytkownika

Składowe systemu

Bazując na szczegółowym opisie technologii OWASP AppSensor można podzielić projekt na niżej wymienione moduły. Każdy z nich postaram się zaimplementować jako oddzielną bibliotekę.

  • Biblioteka do rejestrowania punktów wykrywania (z regułami – z możliwością wyniesienia konfiguracji poza aplikację):
    • definicje punktów detekcji
    • definicje sposobów reakcji
  • Biblioteka do automatycznego podpinania punktów wykrywania do WebAPI
  • Biblioteka rejestrująca zdarzenia podejrzane
  • Serwer logowania (własny i integracje z gotowymi w chmurze) [Kibana, Analogi, Loggly, Splunk]
  • Pulpit podsumowujący zagrożenia w czasie rzeczywistym
  • Aplikacja demonstracyjna
  • Testy dla aplikacji demonstracyjnej

Planowane zadania

Zadania, którymi się zajmuję będę na bieżąco rejestrował w odpowiedniej planszy Trello. Podzieliłem je na 5 grup:

  • Planned – pomysły
  • ToDo – do zrobienia w najbliższym czasie
  • In progress – w trakcie implementacji
  • Done – zrobione
  • Released – skończone i zamknięte w nowej wersji biblioteki

Implementację zacznę od warstwy testowania zagrożeń, przez aplikację demonstracyjną, aż do implementacji samych mechanizmów wewnętrznych. Nie będę tutaj wypisywał konkretnej listy zadań, bo jest ona dostępna publicznie i z natury dość dynamiczna.

Do dzieła 🙂

Konfiguracja TypeScript – kompilacja

Konfiguracja TypeScript – kompilacja

Co to jest TypeScript?

TypeScript to język, który jest nakładką na JavaScript. Udostępnia on silne typowanie dla tego dynamicznego języka. Dodatkowo wprowadza on klasy, moduły i interfejsy, które pomagają w organizacji kodu aplikacji. Jest to szczególnie użyteczne w dużych projektach z wydzielonymi wieloma częściami aplikacji. Dzięki użyciu mechanizmów obiektowych możemy porządkować kod, a dzięki silnemu typowaniu każdy błąd składniowy zostanie wykryty już podczas kompilacji lub, w niektórych edytorach, nawet podczas pisania. Dobra konfiguracja TypeScript pozwoli nam w dużej mierze zautomatyzować ten proces.

Dużym plusem języka TypeScript jest jego pełna kompatybilność z JavaScript. Każdy plik napisany w JS po zmianie rozszerzenia na .ts jest od razu poprawnym plikiem TypeScriptowym. Pozwala to na stosunkowo łatwą migrację projektu napisanego w JS. Jednak warto też wspomnieć o minusach TypeScriptu. Kłopoty zaczynają się, gdy chcemy skorzystać z biblioteki, do której nie są dostępne pliki typowań (definicje typów dla mechanizmu silnego typowania). Typowania dla wielu bibliotek jesteśmy w stanie znaleźć na https://github.com/DefinitelyTyped/DefinitelyTyped. Niestety, gdy chcemy skorzystać z mniej popularnych bibliotek lub choćby z najnowszych wersji, możemy nie znaleźć odpowiednich definicji. Ponieważ typowania są nam potrzebne głównie do sprawdzania poprawności użytych typów danych, możemy skompilować nasze pliki do JS, licząc się z tym, że będą one generować błędy lub ostrzeżenia kompilacji.

Dalej pokażę jak skonfigurować kompilację TypeScript w różnych środowiskach.

Konsola

Podstawowym miejscem obsługi kompilatora TypeScript jest konsola. Zacznijmy od instalacji, którą najlepiej przeprowadzić za pomocą npm

npm install -g typescript

Następnie potrzebny jest plik konfigurujący dla kompilatora. Definiujemy w nim parametry, mówiące o dialekcie, do którego chcemy skompilować kod (ES4, ES5 lub ES6), kompilowanych plikach lub też generowaniu sourceMap. Dokładny opis parametrów znajduje się w dokumentacji https://github.com/Microsoft/TypeScript/wiki/tsconfig.json. Podstawowym ustawieniem może być:

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "removeComments": true,
        "preserveConstEnums": true,
        "sourceMap": true
    },
    "exclude": [
        "node_modules",
        "dist"
    ]
}

Powoduje ono kompilacje wszystkich dostępnych plików .ts do plików .js oraz do sourceMap z katalogu bieżącego i katalogów w nim zawartych. Dodatkowo czyści wynikowe pliki z nadliczbowych komentarzy i definicji.

Teraz wystarczy wywołać komendę

tsc

Pozwala ona na kompilowanie wszystkich plików w naszym projekcie. Pliki .ts zostaną przetworzone do plików .js w tym samym folderze. Można skorzystać z ułatwienia kompilacji i założyć czujkę na plikach do kompilacji. To pozwoli na kompilowanie plików w tle przy każdym zapisie.

tsc -w

Gulp

Gdybyśmy chcieli włączyć proces kompilacji do procesu przetwarzania naszego projektu np. przy pomocy Gulp, musimy skorzystać z odpowiedniej biblioteki

npm install gulp-typescript --save-dev

następnie możemy dodać poniższe kroki budowania do naszego pliku konfiguracyjnego gulpfile.js

var gulp = require('gulp');
var ts = require('gulp-typescript');
var tsProject = ts.createProject('tsconfig.json');

gulp.task('scripts', function () {   
    var tsResult = tsProject.src().pipe(ts(tsProject));
    return tsResult.js.pipe(gulp.dest('.'));
});

Dokładniejszy opis można znaleźć w http://blah.winsmarts.com/2015-7-Setting_up_TypeScript_with_Visual_Studio_Code.aspx

VisualStudio Code

Używając Visual Studio Code do edycji projektów w TypeScript, można skonfigurować sposób budowania aplikacji, który wykorzystuje kompilator TypeScript. W tym celu wystarczy dodać w pliku tasks.json, znajdującym się w klatalogu .vscode, odpowiednią konfigurację dla kompilacji

{
    "version": "0.1.0",
    "command": "tsc",
    "isShellCommand": true,
    "showOutput": "silent",
    "problemMatcher": "$tsc"
}

https://code.visualstudio.com/Docs/languages/typescript
https://cmatskas.com/typescript-and-vs-code/

W codziennej pracy pomocne jest uporządkowanie naszego projektu tak, aby nie wyświetlał generowanych plików. Aby to zrobić, należy edytować ustawienia workspace (File | Preferences | Workspace Settings), a następnie dodać ukrycie plików sourcemap i warunkowe ukrycie plików js

"files.exclude": {
    ...
    "**/*.js": { "when": "$(basename).ts"},
    "**/*.js.map": true
} 

https://code.visualstudio.com/Docs/languages/typescript#_hiding-derived-javascript-files

Przykładowe kody źródłowe można znaleźć na GitHub:

https://github.com/suvroc/InvestDashboard/releases/tag/v0.0.1

Pomysły i zdolność ich wykonania

Pomysły i zdolność ich wykonania

W ostatnim poście pisałem o podjęciu wyzwania, które polega na realizacji otwartego projektu. Decyzję o udziale w tym przedsięwzięciu podjąłem szybko, ale największy problem miałem z pomysłem na ten projekt. Przy czym nie był to problem związany z wymyśleniem czegoś, ale z wyborem spośród tego, o czym myślę ostatnio. Dość często przychodzą mi do głowy różne idee np. na zautomatyzowanie lub ulepszenie jakiegoś procesu czy uwspólnienie czegoś dla kilku aplikacji. Podejrzewam, że wielu developerów ma podobnie. Wiadomo, że jedne pomysły są lepsze, a drugie gorsze. Właśnie dlatego ostatnio staram się jak najszybciej zapisywać swoje koncepcje, tak “na gorąco”. Dzięki temu, gdy mam ochotę i możliwości czasowe, by rozpocząć nowy projekt, nie tracę zapału na długim wymyślaniu, czym mógłbym się zająć. Tylko po prostu przeglądam listę i dokonuję wyboru jak z gotowego menu, którego jedyną wadą jest to, że bardzo wiele potraw wygląda apetycznie.

Zarządzanie pomysłami

Próbowałem już kilku różnych metod zarządania swoimi pomysłami i wypracowałem rozwiązanie, które sprawdza się najlepiej.

Najważniejszymi założeniami są dla mnie:

  • możliwość zapisywania pomysłów w dowolnym momencie
  • łatwa dostępność do listy pomysłów
  • możliwość dodawania komentarzy lub powiązanych informacji

Moja metoda jest prosta – polega na noszeniu przy sobie małego notesika, w którym mogę zapisać każdy pomysł. Dodatkowo co jakiś czas siadam z nim do komputera, by przepisać pomysły do Trello. Narzędzie to służy do tworzenia list zorganizowanych w tablicę podobną do tablicy Kanban. Dzięki temu mogę dowolnie kategoryzować pomysły, decydować się na ich rozpoczęcie oraz podpinać znalezione powiązane materiały. Tego typu rozwiązanie bardzo dobrze sprawuje się w moim przypadku. Na tę chwilę jedynym ulepszeniem, które mogłoby Wam przypaść do gustu, jest według mnie użycie aplikacji do tworzenia notatek w telefonie zamiast notesu.

Zdolność wykonania

Czasem żałuję, że nie jestem w stanie zrealizować wszystkich pomysłów i dlatego chcę być pewien, że przystępując do realizacji danej koncepcji, dokonuję dobrego wyboru. Muszę więc określić na czym mi w danej chwili zależy i jakie mam szanse na sfinalizowanie projektu, w tym celu oceniam swoją zdolność do wykonania projektu (project execution capability).

Jeżeli rozważany pomysł dotyczy dziedziny, na której się kompletnie nie znam i nie czuję potrzeby jej zgłębiania, to uznaję ten pomysł jako nieodpowiedni dla mnie ze względu na brak możliwości wykonawczych. Nie mówię tu o technicznych aspektach, bo wiele projektów jest dla mnie materiałem do nauczenia się lub przećwiczenia pewnych bibliotek lub technik. Mowa o tematykach biznesowych danych projektów. Jeżeli dla przykładu nie znam się na dietetyce, to wiem, że trudno mi będzie wykonać dobrze pomysł z tym związany. Oczywiście, mam też kilka innych kryteriów oceny moich idei, lecz ta jest najważniejsza i zawsze od niej zaczynam.

Dzielenie się pomysłami

Przy dość szybkim odrzucaniu niektórych pomysłów, czasem takich, które uważam za całkiem dobre, mam wrażenie, że pewne ulepszenia codzienności nie ujrzą światła dziennego. Dlatego przyszło mi do głowy, że być może dobrym nawykiem byłoby upublicznianie swoich pomysłów, którymi z różnych względów (np. brak wspomnianej wyżej zdolności do wykonania) postanowiłem się nie zajmować. Wówczas byłaby szansa, że ktoś je zrealizuje w wersji zaproponowanej przeze mnie lub po dokonaniu własnych modyfikacji, a może zainspirowany którąś z koncepcji wymyśli coś znacznie lepszego.

Co myślicie na ten temat? Czy Wy chcielibyście się podzielić swoimi koncepcjami lub poczytać nad czym myśleli inni?
Jak z kolei Wy dbacie o swoje pomysły?

Cache oparty na czasie

Cache oparty na czasie

Walidatory

Wyróżniamy 2 rodzaje walidatorów zasobów:

  • silne – występują wtedy, gdy każda zmiana zawartości zasobu powoduje wykrycie zmiany w walidatorze
  • słabe – gdy wykrywanie zmian w walidatorze opiera się na danych niezależnych od wartości zasobu (np. na informacjach o czasie)

Zgodnie z powyższą definicja wyróżniamy dwa rodzaje cache’a:

Cache oparty na czasie

Polega on na tym, że przechowywany zasób może być używany tylko przez zdefiniowany czas. Do tego celu używany jest nagłówek: Last-Modified, który wskazuje na datę ostatniej modyfikacji zasobu.

Last-Modified: Tue, 15 Nov 2015 12:45:26 GMT

Data ta jest zapamiętywana razem z rekordem i podczas wysłania następnego zapytania GET lub HEAD, dodawany jest warunkowy nagłówek sprawdzający, czy data ta się zmieniła

If-Modified-Since: Tue, 15 Nov 2015 12:45:26 GMT

Jeżeli zasób nie został od tego czasu zmieniony zwrócony zostanie zasób przechowywany w cache ze statusem 304 (Not Modified)

Nagłówek ten jest używany w następujących przypadkach:

  • aby umożliwić działanie cache opartym na wartościach czasowych
  • aby ograniczyć zakres przeglądania zasobów sieciowych tylko do rekordów zmienionych od czasu poprzedniego zapytania

Istnieje także nagłówek If-Unmodified-Since, który powoduje przesłanie treści zasobu tylko gdy nie został on zmodyfikowany od pewnego momentu. Nie jest on bezpośrednio wykorzystywany przez cache, ale ma 2 zastosowania:

  • gdy pobieramy pewien zasób partiami, przy pobieraniu kolejnej cześć chcemy się upewnić, czy nie został on zmodyfikowany i pobieramy cały czas ten sam zasób
  • dla zapytań modyfikujących rekord (np. PUT) możemy w ten sposób zaimplementować mechanizm sprawdzania czy rekord nie został w międzyczasie przz kogoś zmieniony i czy nie chcemy zaktualizować go bazując na nieaktualnej wersji. Jest to rozwiązanie problemu równoczesnego wykorzystywania zasobów.
Sposób działania cache

Sposób działania cache

Nagłówek Vary

Nagłówek Vary umożliwia określenie, które elementy wiadomości (poza standardowymi: metodą, adresem i hostem) jednoznacznie identyfikują dany zasób. Jest to szczególnie przydatne podczas korzystania z mechanizmu Content negotiation.

Dla przykładu odpowiedź zawierająca

Vary: accept-language

informuje, że nagłówek accept-language powinien być uwzględniony w kluczu obiektów cache. Może to wynikać z faktu, że nagłówek ten pozwala ustalić z klientem język wyniku zapytania, a więc dla każdego języka odpowiedź może się znacząco różnić.

Nagłówek Vary może przyjmować wartości:

  • * – oznacza, że wszystkie nagłówki mają znaczenie podczas identyfikacji odpowiedzi
  • lista nagłówków – wymienia nagłówki, które mają znaczenie.

Używając nagłówka Vary możemy sprawić, że 2 poniższe zapytania będą przez mechanizm cache rozpatrywane jako różne:

GET /resource HTTP 1.1
Host: example.com
Content-Type: text/html
Accepted-Language: pl-PL
Vary: accept-language
GET /resource HTTP 1.1
Host: example.com
Content-Type: text/html
Accepted-Language: en-US
Vary: accept-language

Serwer powinien definiować pola Vary w odpowiedzi tylko, jeżeli wygenerowana odpowiedź zależy ściśle od wartości tych pól. Jednak nie powinien on uwzględniać pól, które według definicji mają wpływ na powodzenie operacji np. nagłówka Authorization.

Sposób działania

Gdy zostaje wykonane zapytanie do serwera, możliwe jest (pod pewnymi warunkami) skorzystanie z zapamiętanej uprzednio zawartości. Posłużymy się przykładem pobierania zasobu przez przeglądarkę internetową. Odpowiedź przechowywana w cache może być ponownie użyta w przypadku, gdy:

  • jest ona aktualna, poprawnie zwalidowana z serwerem lub może być użyta przeterminowna (stale state). Szczegóły w kolejnym temacie Cache-Control
  • istnieje wpis w cache do szukanego URI i zdefiniowanego (w nagłówku Vary) zbioru nagłówków
  • użyta metoda umożliwia skorzystanie z zapamiętanej poprzednio wartości
  • nie jest użyty nagłówek no-cache
  • nie jest użyte szyfrowane połączenie HTTPS

W pewnych sytuacjach możliwe jest zwrócenie zachowanej odpowiedzi bez sprawdzenia jej aktualności. Tak może być w przypadku zerwania połączenia do serwera. Przeglądarka może wyjść z założenia, ze lepiej pokazać starszą wersją niż błąd ładowania danych.

Metoda PATCH

Metoda PATCH

Zajmiemy się także metodą PATCH. Nie jest ona wprawdzie częścią standardu HTTP, jednak dość dobrze uzupełnia go w kwestii operacji częściowego aktualizowania danych. Przypomnijmy, że metoda PUT służy do zastąpienia danego zasobu, zaś POST do jego wstawienia. W sytuacji, gdy potrzebujemy zaktualizować tylko kilka właściwości rekordu, zastosujemy metodę PATCH.

W treści wiadomości PATCH znajduje się ciąg informacji o tym, jakie pole i w jaki sposób zmienić. Jeżeli wskazywany zasób jeszcze nie istnieje, może on zostać utworzony, jeżeli informacje o zmianach zawierają wszelkie wymagane parametry.

PATCH /resource HTTP/1.1
Host: www.example.com
Content-Type: application/json
If-Match: "a1b2c3d4"

[
  { "op": "replace", "path": "/prop", "value": 42 },
]

Z uwagi na to, że niektóre instrukcje zmiany wartości bazują na poprzedniej wartości danej właściwości (np. dołączenie tekstu) spory problem może stanowić równoczesne wykonywanie tych operacji. Stan po wykonaniu różnych operacji może nie być jednoznacznie określony, więc szczególnie istotne jest korzystanie z zapytań warunkowych.

Kolejną istotną właściwością tego typu operacji jest jej atomowość. Zestaw zmian zdefiniowany w zapytaniu powinien być naniesiony w całości lub w ogóle.

Wyróżniamy dwie możliwości sprawdzenia, czy możliwe jest używanie metody PATCH dla danego zapytania:

  • Użycie metody OPTIONS,
  • Odczytanie nagłówka Accept-Patch w odpowiedzi serwera.

Jak widać w powyższym przykładzie treść wiadomości PATCH powinna mieć specjalny format. Wynika to z charakteru omawianej metody i tego, że obsługuje ona tylko informacje o zmianach. Jeden z możliwych formatów wiadomości jest opisany w standardzie http://tools.ietf.org/html/rfc6902 Wygląda on następująco:

[
  { "op": "test", "path": "/prop", "value": "foo" },
  { "op": "remove", "path": "/prop" },
  { "op": "add", "path": "/prop", "value": [ "foo", "bar" ] },
  { "op": "replace", "path": "/prop", "value": 12 },
  { "op": "move", "from": "/prop", "path": "/prop2" },
  { "op": "copy", "from": "/prop", "path": "/prop2" }
]

Możliwe operatory to:

  • Add
  • Remove
  • Replace
  • Move
  • Copy
  • Test

Są to informacje o zmianach w formacie JSON. Możliwe jest także przesyłanie delt dla zmian w formacie XML http://tools.ietf.org/html/rfc5261, jednak nie będziemy się tym szerzej zajmować.

Istnieje wiele implementacji mechanizmu PATCH. Przykładem implementacji dla .NET jest https://github.com/myquay/JsonPatch

Informacje dodatkowe:

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
https://www.mnot.net/blog/2012/09/05/patch