niedziela, 5 maja 2013

Visual Studio 2012 Update 2

Na początku kwietnia zostało opublikowane uaktualnienie do Visual Studio 2012 z numerkiem 2.
Co nowego?
Przede wszystkim rozwój narzędzi przeznaczonych do zarządzania, planowania projektu. Dodano nowe funkcjonalności do tablicy kanbanowej. Dodano także możliwość tagowania elementów roboczych co ułatwia zarządzanie elementami w projekcie.

W samym Visual Studio wprowadzono możliwość grupowania testów na podstawie klas, jak i możliwość tworzenia list testów. Dzięki takiemu rozwiązaniu, możliwe jest stworzenie kilku list testów.

Kolejnym bardzo fajnym narzędziem jest Code Map - o tym narzędziu mam zamiar napisać osobny post. Narzędzie usprawnia proces debugownaia i odnajdowania problemów w naszej aplikacji.

Zachęcam do aktualizacji i podzielenia się swoimi opiniami nt. nowej aktualizacji.

Polecam także filmik pokazujący na żywo nowości wprowadzone w Update 2:


sobota, 6 kwietnia 2013

ASP.NET MVC Html.Action

Dzięki metodzie Html.Action możemy w łatwy sposób generować zawartość strony przez generowanie jej części po stronie serwera.

Renderowana zawartość może być uzależniona od dodatkowych parametrów, które prześlemy do metody która generuje daną cześć.

Na początek wybieramy miejsce w którym ma zostać wyrenderowana nowa treść:

Code:
    </li>
        @Html.Action("Three")
    <li class="three">

Następnie tworzymy w kontrolerze metodę Three:

Code:
        public ActionResult Three()
        {
            return PartialView("Three");
        }

oraz widok jej odpowiadający:

Code:
Some text to render


To wszystko co jest wymagana do generowania i wyświetlania zawartości takiej metody.

Pozostaje jeszcze jedna kwestia. Jak wiadomo, każdą publiczną metodę w kontrolerze można wywołać z paska przeglądarki. Jeżeli jednak nie chcemy aby naszą metodę, która ma zostać wywołana tylko na innej strony, możemy zastosować atrybut [ChildActionOnly]:

Code:
        [ChildActionOnly]
        public ActionResult Three()
        {
            return PartialView("Three");
        }

ASP.NET MVC Sekcje

Sekcje zostały wprowadzone do ASP.NET MVC w wersji 3. Umożliwiają one wyznaczenie miejsca gdzie, zostanie załadowana dodatkowa treść.

Sekcję definiujemy w sposób bardzo prosty, np. w pliku _Layout:

Code:
@RenderSection("MySection", false)


Parametry które przekazujemy to:
  • nazwa sekcji
  • flaga oznaczająca czy dana sekcja jest wymagana
Jeżeli oznaczymy, że dana sekcja jest wymagana, a np. na którejś ze stron jej nie dodamy, zostanie wyświetlony błąd.

Po wskazaniu miejsca w którym ma zostać wyświetlona sekcja, definiujemy jej treść na poszczególnych stronach, np. na stronie Index.cshtml:

Code:
@section MySection
{
    Dodatkowe informacje o Index
}


Treść zostanie wyświetlona na stronie Index w miejscu gdzie utworzyliśmy sekcję (w moim przypadku pod logiem):


Możemy także wyświetlić w sekcji domyślny tekst, jeżeli na którejś ze stron nie zostanie zdefiniowana:

Code:
                        @if (IsSectionDefined("MySection"))
                        {
                            @RenderSection("MySection", false);
                        }
                        else
                        {
                            <label>Some default text...</label>
                        }


Tak więc stosując metodę IsSectionDefined sprawdzamy czy dana sekcja jest zdefiniowana.

poniedziałek, 1 kwietnia 2013

Walidacja ASP.NET MVC 4

O walidacji pisałem już wcześniej w kursie o ASP.NET MVC 2.
W wersji trzeciej i czwartej dodano nowe możliwości walidacji o których napiszę w tym poście.


1. Atrybuty
Atrybuty opisywałem w przypadku wersji drugiej ASP.NET MVC - nie będę więc powielał już napisanego tekstu.
Dla przypomnienia tylko w jaki sposób je dodajemy do naszego modelu:

Code:
        [StringLength(50)]
        public string FirstName { get; set; }

Dodano nowe atrybuty, wśród nich m.in: EmailAddress, Url, CreditCard, Phone, Remote, Display, DisplayFormat.

Ostatni atrybut - Remote pozwala na napisanie kodu, który wykonuje walidację po stronie serwera bez potrzeby wysyłania całego formularza do serwera. Bardzo dobry przykład ilustrujący zastosowanie tego atrybutu można znaleźć w msdn http://msdn.microsoft.com/en-us/library/gg508808%28v=vs.98%29.aspx


2. Napisanie własnego atrybutu walidacji
Jeżeli kilka właściwości w modelu wymaga tego samego sposobu walidacji, można go zamknąć w atrybucie dziedziczącym po klasie ValidationAttribute:

Code:
    public class CustomValidationAttribute : ValidationAttribute
    {
        public CustomValidationAttribute() : base("{0} is not valid")
        {
            
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            //Validacja

            return ValidationResult.Success;
        }
    }

W naszej klasie nadpisujemy metodę IsValid i umieszczamy w niej kod odpowiedzialny za walidację wartości. w przypadku powodzenia zwracamy wartość Success, w innym przypadku zwracamy obiekt ValidationResult z wiadomością błędu.


3. Interfejs IValidatableObject
Zaletą tego rozwiązania jest to, że mamy dostęp do całego obiektu modelu w momencie przeprowadzania walidacji. W przypadku atrybutów możemy przeprowadzić walidację więcej niż jednego pola na raz, jednak korzystając z interfejsu IValidatableObject jest to o wiele prostsze.
Przykład:

Code:
    public class ApplicationUser : IEditModel, IValidatableObject
    {
        public int Id { get; set; }

        [StringLength(50)]
        public string FirstName { get; set; }

        public string LastName { get; set; }
        public bool SecretField { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (FirstName.Length > 30 && LastName.Length > 30)
            {
                yield return new ValidationResult("Something is wrong...");
            }
        }
    }

niedziela, 31 marca 2013

ASP.NET MVC Mass Assignment

Mass Assignment to sposób na wysłanie parametrów które normalnie nie powinny się znaleźć w żądaniu POST.

Zobaczmy na prosty przykład obrazujący opisany powyżej problem:

Model:

Code:
    public class ApplicationUser
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool SecretField { get; set; }
    }

Na podstawie modelu tworzymy widok edycji danych użytkownika:

Code:
@model MassAssignment.Models.ApplicationUser

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>ApplicationUser</legend>

        @Html.HiddenFor(model => model.Id)

        <div class="editor-label">
            @Html.LabelFor(model => model.FirstName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
        @*
        <div class="editor-label">
            @Html.LabelFor(model => model.SecretField)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.SecretField)
            @Html.ValidationMessageFor(model => model.SecretField)
        </div>
        *@
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}


Pole SecreetField zostało za komentowane (nie pojawi się w źródłach wygenerowanego dokumentu).

W kontrolerze wprowadzamy prosty kod który uaktualni obiekt użytkownika na podstawie przekazanych przez Binding wartości:

Code:
        [HttpPost]
        public ActionResult Edit(ApplicationUser user)
        {
            if (ModelState.IsValid)
            {
                var u = userService.GetUserById(user.Id);
                UpdateModel(u);
            }

            return RedirectToAction("GetUsers");
        }

Rozwiązanie można powiedzieć książkowe - poza jednym szczegółem - brakuje SaveChanges na obiekcie Context, którego akurat w tym przypadku nie mam (nie korzystam z bazy danych a ze statycznej kolekcji).

Wydawałoby się że powyższy przykład jest poprawny. Jednak jest jedna rzecz którą przeoczyliśmy podczas tworzenia tego rozwiązania - nasi użytkownicy. Zobaczmy co mogłoby się stać, gdyby ktoś chciał wykorzystać aplikację w sposób który nie chcemy:


Przepraszam za słabą jakość, ale chodzi tutaj o samo sedno problemu. Mianowicie jeżeli użytkownik będzie chciał zaatakować naszą stroną i w naszym modelu znajdzie się pole typu IsAdmin, użytkownik może przesłać ten parametr bezpośrednio do mechanizmu bindowania (np. tak jak ja za pomocą QueryString).

W jaki sposób możemy zapobiec takiemu zachowaniu? Otóż istnieje kilka rozwiązań:

1. Stworzenie osobnych modeli do edycji, wyświetlania
2. Skorzystanie z atrybutu Bind:

Code:
public ActionResult Edit([Bind(Exclude = "SecretField")]ApplicationUser user)

Kolejne pola, które nie mają brać udziału w bindowaniu wymieniamy po przecinku. Możemy oczywiście także wskazać, które pola mają brać udział w bindowaniu.

3. Metoda UpdateModel jako jeden z argumentów przyjmuje tablicę pól, które mają zostać uaktualnione:

Code:
UpdateModel(u, includeProperties: new string[] {"FirstName", "LastName"});

4. Kolejną możliwością jest stworzenie np. interfejsu:

Code:
    public interface IEditModel
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

następnie w metodzie UpdateModel podajemy ten model:

Code:
UpdateModel<IEditModel>(u);


5. Jeszcze jedną możliwością, jest nałożenie atrybuty ReadOnly na dane pole w modelu:

Code:
[ReadOnly(true)]

Mechanizm bindowania respektuje tak nałoży atrybut na dane pole.

Zakończenie certyfikacji

29 marca oficjalnie zakończyłem ścieżkę certyfikacyjną - przynajmniej na ten moment :)

Efektem nauki jest zdanie 4 egzaminów MCTS, oraz dwóch MCPD.

70-513
Windows Communication Foundation - egzamin prosty, chociaż w niektórych przypadkach czytanie XMLów może zająć trochę więcej czasu. Materiały z których się uczyłem to Training Kit dla .NET 3.5, tematy które nie są zawarte w podręczniku można doczytać w internecie. Warto też przejrzeć materiały zebrane w formie linków na stronie http://www.jamesjfoster.com/blog/2010/resource-links-for-70-513-wcf-certification-exam/. Wiedza ta wystarcza w 100% do zdania egzaminu.

70-518
Egzamin różni się od pozostałych tym, że kładzie nacisk na znajomość ograniczeń i możliwości poszczególnych rozwiązań Microsoft podczas tworzenia aplikacji desktopowych. Większość pytań dotyczy tworzenia systemów i pytań mających na celu określenie wyboru prawidłowego frameworka / rozwiązania danego zagadnienia.

70-515
Tworzenie aplikacji webowych w ASP.NET WebForms oraz MVC. Pierwszym zastrzeżeniem jakie mam co do egzaminu to użycie MVC w wersji 2.0. Mamy już 4 wersję tego frameworka i raczej żadnego nowego projektu nie rozpoczniemy pisać w czymś co powstało kilka lat wstecz. Jeżeli chodzi o poziom pytań - zróżnicowany od bardzo prostych i wręcz oczywistych aż do zagadnień, których często nie wykorzystuję.

70-519
Egzamin który ma tę samą formę co 70-518. Najdłuższy ze wszystkich, największa ilość pytań którą do tej porty otrzymałem - 78. Podczas egzaminu dwa przypadki systemów do których zostało przygotowanych 14 pytań. Ogólnie egzamin podobał mi się, ciekawe pytania i zagadnienia. Jeżeli ktoś pracuje przy tworzeniu aplikacji webowych po przeczytaniu materiałów zawartych w Training Kit i skorzystaniu z linków do materiałów zawartych w nim, nie będzie mieć problemów ze zdaniem ich.



Obecnie nie zamierzam przystępować do kolejnych egzaminów. Sama certyfikacja posłużyła mi głównie w celu segregacji posiadanej wiedzy, nauczeniu się nowych metod rozwiązywania często spotykanych problemów.
W sieci można spotkać się z opiniami, że certyfikacja nie ma sensu. Nie zgodzę się z tymi stwierdzeniami. W wielu przypadkach samo podejście do egzaminu zmusza nas do przeczytania odpowiednich materiałów, dzięki którym być może nauczymy się rozwiązywać dane problemy w lepszy, szybszy i bardziej przenośny sposób.
Wszystkim, którzy są w trakcie bądź też przymierzają się do certyfikacji życzę wytrwałości i sukcesów.

sobota, 30 marca 2013

Parsowanie HTML w .NET

Parsowanie HTML chociaż wydaje się rzeczą trywialną potrafi przysporzyć wielu problemów. Związanie jest to nie tyle ze skomplikowaniem zagadnienia co z formatem w jakim otrzymujemy dane.
Szukając tagu w wielu przykładach możemy natrafić na nie zamknięte tagi, niepoprawnie sformatowane znaczniki itp.

Jednym z lepszych parserów dostępnych w .NET jest biblioteka Html Agility Pack http://htmlagilitypack.codeplex.com/

Do projektu bibliotekę można dodać poprzez ściągnięcie odpowiedniej dll z codeplex, bądź korzystając z NuGet-a:

PM> Install-Package HtmlAgilityPack

Po zainstalowaniu biblioteki możemy przystąpić do przetwarzania HTML:

Code:
using System;
using System.Collections.Generic;
using System.Net;
using HtmlAgilityPack;
using System.Linq;

namespace HtmlAgilityPack_Sample
{
    class Program
    {
        private static void Main(string[] args)
        {
            var webClient = new WebClient();
            string pageContent = webClient.DownloadString(@"http://www.onet.pl/");
            var htmlDocument = new HtmlDocument();
            htmlDocument.LoadHtml(pageContent);

            foreach (HtmlNode link in htmlDocument.DocumentNode.SelectNodes("//a[@href]"))
            {
                HtmlAttribute att = link.Attributes["href"];
                Console.WriteLine(att.Value);
            }

            foreach (var div in htmlDocument.DocumentNode.SelectNodes("//div"))
            {
                HtmlAttribute classAttribute = div.Attributes["class"];
                if (classAttribute != null)
                {
                    Console.WriteLine(classAttribute.Value);
                }
            }

        }
    }
}


Przykładowy kod powyżej pokazuje w jaki sposób pobrać ze strony wszystkie linki i znaczniki div. Zapytania pisane są w języku XPath.

Biblioteka jest dostępna jako opensource - możemy więc pobrać kod i dodawać nową funkcjonalność.

Na temat biblioteki można znaleźć krytyczne wypowiedzi, dotyczące m.in.:
  • w przypadku gdy metoda SelectNodes() nie zwróci rezultatów, zwracany jest null co uniemożliwia użycie takiego rezultatu jako rezultatu wyrażenia np. pętli foreach
  • kiedy szukamy dzieci podanego węzła, metoda SelectNode szuka od początku dokumentu (aby szukała od aktualnego węzła należy podać parametr descendant::)
Z moich testów wynika, że biblioteka dobrze radzi sobie z parsowaniem nawet bardzo kiepskiej jakości kodu HTML. Wyżej wymienione problemy można samemu poprawić (kod jest dostępny). Zachęcam do eksperymentowania i podzieleniem się uwagami na temat biblioteki.