Pierwsza zasada programisty: nigdy nie ufaj danym wejściowym. Sama walidacja tych danych może zostać przeprowadzona na wiele sposobów, przekazując odpowiedzialność od warstwy najwyższej do najniższej. Takie podejście pozwala na eliminowanie zagrożenia w wielu przypadkach jeszcze bez przesłania danych do serwera. (Jeśli ktoś nie lubi czytać przydługich wstępów, może przejść od razu do punktu: "Dlaczego xVal?").
Walidacja po stronie warstwy prezentacji <asp:TextBox ID="TextBox1" runat="server" />
<asp:RequiredFieldValidator ID="reqVal" runat="server"
ErrorMessage="Please enter a value" ControlToValidate="TextBox1" />
<asp:Button ID="Button1" runat="server" Text="Button" />
Należy pamiętać, że walidacja po stronie klienta (JavaScript) ZAWSZE musi iść w parze z walidacją po stronie serwera. W przeciwnym razie, to tak jak by w ogóle tej walidacji nie było! Wywołanie metody PageIsValid:
protected void Button1_Click(object sender, EventArgs e)
{
if (Page.IsValid)
{
// TextBox1 posiada wartość.
}
}
Walidacja po stronie logiki biznesowejprivate static void Validate (EmailAddress myEmailAddress)
{
if (myEmailAddress.ContactPersonId <= 0)
{
throw new InvalidOperationException("Błąd, brak odpowiedniego ContactPersonId!");
}
if (string.IsNullOrEmpty(myEmailAddress.Email))
{
throw new InvalidOperationException("Błąd, brak emaila!");
}
if (!IsValidEmailAddress(myEmailAddress.Email))
{
throw new InvalidOperationException("Błąd, wprowadzony email jest błędny!");
}
}
public static int Save(EmailAddress myEmailAddress)
{
Validate (myEmailAddress);
myEmailAddress.Id = EmailAddressDB.Save(myEmailAddress);
return myEmailAddress.Id;
}
Walidacja po stronie serwera SQLALTER TABLE dbo.Category ADD CONSTRAINT
CK_Category CHECK (SortOrder >= 0 AND SortOrder <= 100)
GO
Wykorzystanie dostępnego frameworka do walidacji danych, np:Przedstawione frameworki różnią się przeznaczeniem. Ja przedstawię w tym poście świeży jeszcze projekt
xVal.
Dlaczego xVal?
- Stworzony do użycia z ASP.NET MVC
- Wspiera walidację client-side oraz server-side
- Możliwość lokalizacji językowej komunikatów
- Oparty o przyjęte "dobre techniki"
- Możliwość włączenia do projektu bez konieczności przeprowadzania "wielkich" zmian
- Lekki, 1 plik .dll do podłączenia
- Open source
- Mało znany, bo nowy :-)
Zaczynamy. xVal - validation framework for ASP.NET MVC applications.
Po stronie modelu, xVal opiera swoje działanie o atrybuty. Z tego względu atrybuty naszych klas musimy odpowiednio oznaczyć atrybutami. W moim projekcie posiadam klasę Order.cs, którą będę poddawał torturom walidacji:
public class Order
{
[Required] [StringLength(15)]
public string ClientName { get; set; }
[Range(1, 20)]
public int NumberOfProducts { get; set; }
[Required] [DataType(DataType.Date)]
public DateTime Date { get; set; }
}
Objaśnienie zastosowanych atrybutów:
- [Required] : dany atrybut jest wymagany, nie może być pusty.
- [StringLength(n)] : dany atrybut musi posiadać odpowiednią długość, dla typu String.
- [Range(n, m)] : dany atrybut musi być z przedziału od, do.
- [DataType(typ)] : dany atrybut musi być zgodny z podanym typem.
Należy jeszcze dodać referencję do:
System.ComponentModel.DataAnnotations.dllNadszedł czas na kontroler. Ja wszystkie operacje dotyczące składania zamówień będę przeprowadzał w
HomeController.
public class HomeController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ViewResult CreateOrder()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateOrder(Order order)
{
OrderManager.PlaceOrder(order);
return RedirectToAction("Completed");
}
public ViewResult Completed()
{
return View();
}
}
Klasa OrderManager:
public static class OrderManager
{
public static void PlaceOrder(Order order)
{
var errors = DataAnnotationsValidationRunner.GetErrors(order);
if (errors.Any())
throw new RulesException(errors);
// Business rule: Nie można składać zamówień w niedzielę
if(order.Date.DayOfWeek == DayOfWeek.Sunday)
throw new RulesException("ArrivalDate", "Nie można skłdać zamówień na niedzielę!", order);
// Todo: zapis do bazy danych, pliku xml lub cokolwiek innego
}
}
Czas na widok CreateOrder.aspx
<h1>Zamówienie pluszowego misia</h1>
<% using(Html.BeginForm()) { %>
<div>
Nazwa: <%= Html.TextBox("order.ClientName") %>
<%= Html.ValidationMessage("order.ClientName") %>
</div>
<div>
Ilość misiów: <%= Html.TextBox("order.NumberOfProducts")%>
<%= Html.ValidationMessage("order.NumberOfProducts")%>
</div>
<div>
Data dostawy: <%= Html.TextBox("order.Date")%>
<%= Html.ValidationMessage("order.Date")%>
</div>
<input type="submit" />
<% } %>
Potrzebujemy jeszcze prostą i niezmienną klasę ValidationRunnera, gdyż DataAnnotations takiej nie posiada:
internal static class DataAnnotationsValidationRunner
{
public static IEnumerable<ErrorInfo> GetErrors(object instance)
{
return from prop in TypeDescriptor.GetProperties(instance).Cast<PropertyDescriptor>()
from attribute in prop.Attributes.OfType<ValidationAttribute>()
where !attribute.IsValid(prop.GetValue(instance))
select new ErrorInfo(prop.Name, attribute.FormatErrorMessage(string.Empty), instance);
}
}
Musimy jeszcze zmodyfikować napisany wcześniej szkielet metody
CreateOrder w kontrolerze
HomeController, tak aby błędy były przechowywane w stanie modelu.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateOrder(Order order)
{
try {
OrderManager.PlaceOrder(order);
}
catch(RulesException ex) {
ex.AddModelStateErrors(ModelState, "order");
}
return ModelState.IsValid ? RedirectToAction("Completed")
: (ActionResult) View();
}
Tym oto sposobem zakończyliśmy walidację po stronie serwera. Czeka nas jeszcze implementacja sprawdzanie po stronie klienta.

Walidacja po stronie klienta (Client-side validation). Wszystko czego będziemy potrzebować do wykonania walidacji za pomocą javascriptu, to biblioteka
jquery.validate.js oraz xVal.jquery.validate.js (ten znajduje się w folderze z projektem xVal). Należy je skopiować do folderu z naszymi skryptami (/Scripts) oraz podlinkować je do naszego MasterPage.
<head>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery-1.2.6.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/jquery.validate.js")%>"></script>
<script type="text/javascript" src="<%= ResolveUrl("~/Scripts/xVal.jquery.validate.js")%>"></script>
</head>
Czas teraz na import helperów xVal, najlepiej zrobić to raz w pliku Web.config:
<system.web>
<pages>
<namespaces>
<!-- leave rest as-is -->
<add namespace="xVal.Html"/>
</namespaces>
</pages>
</system.web>
Wracamy do widoku oraz dodajemy linijkę służącą do wymuszenia walidacji po stronie klienta. Będzie ona zastosowana dla kontrolek prefixowanych przez nazwę
order:
<%= Html.ClientSideValidation<Order>("order") %>
Od tej pory walidacja po stronie klienta powinna być uruchamiana, "potajemnie" korzystając oczwyiście z atrybutów, które definiowaliśmy wcześniej. Super!
W załączniku przesyłam zlokalizowany przeze nnie na nasz język plik z komunikatami.