Aplicație Web Profesională de Recrutare It
Aplicație web profesională de recrutare IT
utilizând ASP.NET MVC
“The hardest single part of building a software system is deciding precisely what to build. No other part of the conceptual work is as difficult as establishing the detailed technical requirements, including all the interfaces to people, to machines, and to other software systems. No other part of the work so cripples the resulting system if done wrong. No other part is more difficult to rectify later.
Therefore, the most important function that the software builder performs for the client is the iterative extraction and refinement of the product requirements.”
-Fred Brooks, The Mythical Man-Month
CUPRINS
1.INTRODUCERE
Domeniul IT reprezintă una dintre cele mai importante ramuri tehnologice ale erei curente. Datorită acestui lucru, numărul firmelor din acest domeniu a crescut considerabil. Implicit, necesitatea de resurse umane competente este indispensabilă. Majoritatea firmelor apelează la mediul online pentru a recruta persoane ce satisfac nevoile firmei. Companiile au observat faptul că metodele proprii sunt costisitoare si nu sunt eficiente. Acest lucru a dus la folosirea unor platforme specializate, ce puteau să îndeplinească aceste nevoi mult mai rapid și mai putin costisitor. Momentan, recrutarea online este cea mai de succes aplicație de business ca metodă de interacționare cu un număr mare de doritori ai unui loc de muncă, fapt ce a determinat firmele să apeleze la această modalitate de recrutare. Timpul necesar pentru a angaja persoane noi este redus, ocupând rapid locurile disponibile. De asemenea, costurile sunt reduse.
Platformele de recrutare sunt asemănătoare platformelor de socializare, cel mai important aspect fiind comunitatea utilizatorilor, dar și cel mai riscant. În urma analizei nevoilor angajatorilor și a candidaților, acestea au dezvoltat aplicațiile de recrutare, implementând un mediu ușor, disponibil oricui care aduce beneficii ambelor parti.
Scopul acestei aplicații a fost de a îndeplini nevoile acestora, prin implementarea unor caracteristici, diferite față de cele ale firmelor de recrutare deja existente. Am ales să dezvolt o aplicație care să se centralizeze pe nevoile dezvoltatorilor și ale companiilor din domeniului IT. Pentru a identifica necesitățile companiilor IT, am analizat piața aplicațiilor de recrutare din România si modalitatea de desfășurare a aplicării la un post de la compania Microsoft. Am observat că majoritatea site-urilor de recrutate se adresează unei arii largi de domenii, deși cele mai multe oferte de muncă sunt pe domeniul IT.
Pentru candidați am oferit posibilitatea de conectare la mediile externe pentru a prelua informațiile lor deja existente și pentru a oferi notificări.
Pentru angajatori, am pus la disponziție posibilitatea de a adăuga unui job si un chestionar dinamic, care să indeplinească nevoile acestora pentru a determina dacă un candidat este potrivit pentru acel post.
2.TEHNOLOGIILE FOLOSITE
În acest capitol se vor prezența tehnologiile utilizate în dezvoltarea acestei aplicații, modul de funcționare și beneficiile aduse.
Aplicația a fost dezvoltată folosind Visual Studio 2013 , un produs Microsoft adresat dezvoltări. Am folosit acest produs datorită framework-ului ASP.NET MVC, utilitar dedicat pentru dezvoltarea aplicațiilor web folosit limbajul de programare C# și a arhitecturi de dezvoltare MVC Microsoft.
Am ales limbajul C# datorită cunoștințelor acumulate prin dezvoltarea aplicatilor în cadrul companiei în care sunt angajat.
Bază de date a fost realizată cu ajutorul aplicației Microsoft SQL Server, dar gestionată printr-un proiect de baze de date creat în Visual Studio.
Cel mai important framework folosit este BForms. Acest framework este dezvoltat în cadrul firmei în care muncesc, find unul dintre cei care contribuie la implementarea și rezolvarea buguri ale acestuia.
Baza de date a fost realizată cu ajutorul aplicației Microsoft SQL Server 2014, dar gestionată printr-un proiect de baze de date creat în Visual Studio.
Pentru interfață, am folosit cel mai cunoscut proiect pentru designeri, și anume Twitter Bootstrap. Acest framework este inclus în BForms, fiind modificat pentru a îndeplinii cerințele componentelor dezvoltate.
Pentru gestionarea acțiunilor utilizatorului și a interacțiunii dintre server și client, am folosit jQuery, un framework dezvoltat peste JavaScript.
În următoarele subcapitole, am să prezint tehnologiile și framework-urile folosite. Pentru C# și jQuery am prezentat cele mai importante aspecte ale acestor limbaje, iar pentru framework-urile ASP.NET MVC și BForms am realizat o prezentare mai amănunțită.
2.1. C#
C# este un limbaj orientat pe obiecte, scopul lui fiind de a îmbunătăți productivitatea dezvoltatorilor prin simplitate, expresivitate și performanță. Autorul acestei arhitecturi este Andrers Heljsberb (creatorul Turbo Pascal și al arhitecturii Delphi). A fost implementat să fie folosit cu framework-ul .NET al Microsoft-ului. Microsoft a dezvoltat acest limbaj pentru a aduce consistență și eleganță unui limbaj orientat pe obiecte, prin implementarea unui mediu de executare și de dezvoltarea a codului realizat de dezvoltatori. Cea mai recentă versiune a C#-ului este 5.0.
Beneficiile aduse mediului de executare:
Securitate
Compatibil pe mai multe platforme de dezvoltare
Performanță
Beneficiile aduse mediului de dezvoltare:
Mediu de dezvoltare orientat pe obiecte
Simplificarea modului de a finaliza un proiect
Independența limbajului
Interoperabilitate (capacitatea de comunica cu alte limbaje .NET, cu sistemul de operare , cu DLL-uri ,etc).
Caracteristicile specifice C#-ului, comparativ cu perspectiva standard a programării orientate pe obiecte:
Unificare tipurilor sistemului : încapsularea datelor și a funcționalităților în tipuri. Astfel orice obiect definit, cuprinde același set de funcționalități, de exemplu, orice tip poate fi convertit într-un șir de caracatere prin metoda ToString.
Clase și interfețe: o interfață este asemănătoare unei clase, singura deosebire fiind faptul că interfața descrie membrii clasei, fără declararea și implementarea lor. În C# moștenirea multiplă nu este posibilă, singura metodă fiind folosirea interfețelor.
Proprietăți, metode și evenimente: metodele sunt funcțiile care cuprind proprietăți și evenimente, proprietățile sunt membrii unei funcții care descriu starea obiectului și evenimentele sunt funcții definite care se aplică obiectului, atunci când starea acestuia se schimbă.
În următoarele subcapitole, am să prezint sumar câteva aspecte ale C#.
2.1.1. CLR (Common Language Runtime)
Mediu de executare al .NET-ului se numește CLR (Common Language Runtime): CLR-url gestionează programele la execuția lor, inclusiv următoarele:
Managementul memoriei și garbage colletion (acest serviciu distruge obiectele din memorie care nu se vor mai folosi în cadrul programului)
Verificarea codului
Executarea codului, managementul firelor de execuție (thread-uri) și manipularea excepțiilor care pot apărea
Securitatea serviciilor
Încărcarea librăriilor
CLR-ul nu este dependent de limbaj, oferind posibilitatea programatorilor să dezvolte aplicații în limbaje diferite (C#, Visual Basic .NET, Deplhi.NET, Chrome .NET, etc.). Codul gestionat este transformat în assembly, fie sub forma de fișier executatil (.exe) sau sub forma unei librării (.dll), împreună cu informațiile de tip sau a metadatelor.
Imaginea de mai jos arată cum codul este compilat și gestionat de CRL:
Fig. 2.1-1 [2]
2.1.2. Identificatori ai limbajului
Identificatori sau keywords (cuvinte cheie) sunt un set de șiruri folosite pentru a defini limbajul C#:
Fig. 2.1-2 [2]
Aceste cuvinte cheie sunt folosite strict de limbajul C#, și nu pot fi folosite, de exemplu, ca nume de variabile, decât dacă sunt prefixate cu caracterul @. În afară de aceste cuvinte cheie, există și un set de cuvinte cheie dependente de context (contextual keywords), fiecare cu o anumită însemnătate în funcție de context. Acestea nu au restricția de a nu fi folosite că identificatori.
Fig. 2.1-3 [2]
2.1.3. Tipuri predefinite
Fig. 2.1-4 [2]
2.1.4. Expresii Lambda
Pentru a explica conceptul și modul de funcționare a expresiilor lambda, trebuie definit conceptul de delegare.
Un delegat este un obiect care știe cum să apeleze o metodă, specificând tipul de returnare și tipul parametrilor. Astfel, dacă creăm o instanță a delegatului și îi atribuim o metodă care are același tip de returnare și aceleași tipuri ale parametrilor, la apelarea unui delegat, practic se apelează metoda cu care a fost instanțiat delegatul.
Exemplu:
delegate string DelegateMethod(int param)
…
private static string ConvertIntToString(int x)
{
return x.ToString;
}
…
DelegateMethod d = ConvertIntToString;
d(200); // mai exact, d.Invoce(3), va afișa 200
O expresie lambda este o metodă fără nume, care ține locul unei instanțieri a unui delegat. Compilatorul transformă o expresie lambda în:
Instanța unui delegat
Sau într-un arbore de expresii, de tipul Expression<TDelegate>, reprezentând codul din interiorul expresiei lambda care parcurge un obiect, lucru ce ne ajută să construim expresii de query-uri.
Delegatul definit mai sus poate fi scris folosind expresii lambda:
DelegateMethod d = x => x.ToString();
O expresie lambda are următoarea formă:
(parametri) => expresie sau bloc de cod
Expresiile lambda sunt folosite cel mai frecvent cu delegatul Func, expresia anterioară fiind scrisă astfel:
Func<int, string> convToString = x => x.ToString();
Aceste expresii sunt folosite în general , împreună cu LINQ pentru a putea face query-uri sau pentru crearea unor Func-uri pentru a mapa un obiect de tip X într-un obiect de tipul Y.
2.1.5. Atribute
Un atribut este un obiect care reprezintă datele asociate unui obiect. Practic se adaugă metadatele în assembly-ului unui program. Elementul care are atașat un atribut este referit că fiind target-ul atributului. Programele care primesc și folosesc metadate, se numesc consumatori de atribute.
.Net-ul are predefinite un set de atribute care pot fi folosite deja, dar se pot define și atribute personalizate. Imaginea următoare specifică procesul prin care trece un atribut:
Fig. 2.1-5 [2]
Compilatorul primește atribute, dar și le consumă.
Atribute pot fi definite fără parametri, dar și cum parametri. Exemplu: [Serializable] // Atribut fara parametru
public class ClasaExemplu1
{ …
[CustomAttribute(2)] // Atribut cu parametrii
public class ClasaExemplu2
{ …
Parametrului unui atribut poate avea următoarele tipuri: bool, byte, char, double, float, int, long, short, string.
Pentru a defini un atribut personalizat, clasa trebuie să fie derivată din clasa System.Attribute, sau dintr-o clasă care a moștenit clasa System.Attribute. Exemplu de atribut personalizat, folosit în aplicație prezentat în această lucrare, pentru diferite validări a permisiunilor unui utilizator:
Fig. 2.1-6
System.Web.Mvc.ActionFilterAttribute > System.Web.Mvc.FilterAttribute > System.Web.Mvc.FilterAttribute > System.Attribute.
Un element poate suporta mai multe atribute, acestea fiind definite pe rând: [Serializable]
[CustomAttribute(2)]
public class ClasaExemplu
{ …
Pentru a accesa metadatele unui obiect, la rulare, trebuie să folosim setul de clase disponibile namespace-ul Reflection, împreună cu clasa System.Type, pentru a putea examina și interacționa cu metadatele.
Reflection-ul oferă următoarele metode pentru a extrage atribute personalizate:
GetCustomAttributes, preia toate informațiile din atributele specificate unui obiect
GetCustomAttribute<T>, unde T este tipul atributului din care se preiau informații
Exemplu pentru accesarea metadatelor unui obiect:
Fig. 2.1-7
2.2. jQuery
Javascript-ul este un limbaj de programare care oferă interacțiune și conținut dina
public class ClasaExemplu1
{ …
[CustomAttribute(2)] // Atribut cu parametrii
public class ClasaExemplu2
{ …
Parametrului unui atribut poate avea următoarele tipuri: bool, byte, char, double, float, int, long, short, string.
Pentru a defini un atribut personalizat, clasa trebuie să fie derivată din clasa System.Attribute, sau dintr-o clasă care a moștenit clasa System.Attribute. Exemplu de atribut personalizat, folosit în aplicație prezentat în această lucrare, pentru diferite validări a permisiunilor unui utilizator:
Fig. 2.1-6
System.Web.Mvc.ActionFilterAttribute > System.Web.Mvc.FilterAttribute > System.Web.Mvc.FilterAttribute > System.Attribute.
Un element poate suporta mai multe atribute, acestea fiind definite pe rând: [Serializable]
[CustomAttribute(2)]
public class ClasaExemplu
{ …
Pentru a accesa metadatele unui obiect, la rulare, trebuie să folosim setul de clase disponibile namespace-ul Reflection, împreună cu clasa System.Type, pentru a putea examina și interacționa cu metadatele.
Reflection-ul oferă următoarele metode pentru a extrage atribute personalizate:
GetCustomAttributes, preia toate informațiile din atributele specificate unui obiect
GetCustomAttribute<T>, unde T este tipul atributului din care se preiau informații
Exemplu pentru accesarea metadatelor unui obiect:
Fig. 2.1-7
2.2. jQuery
Javascript-ul este un limbaj de programare care oferă interacțiune și conținut dinamic paginilor HTML. jQuery este un framework construit pe baza funcționalităților limbajul JavaScript. Conform documentației, jQuery este descris ca o librărie JavaScript rapidă, mică ca dimensiune și un set mare de caracteristici oferite, care simplifică modul de accesare a elementelor dintr-o pagină web, de manipulare a evenimentelor, de animare a elementor și a interacțiunii Ajax pentru a dezvolta aplicații web mult mai rapid. [4]
jQuery este cea mai folosită librărie de JavaScript, datorită modului cum a fost dezvoltat și este folosită de multe companii precum : Microsoft, Twitter, Facebook, etc.
Datorită modului de abstractizare al limbajului, acesta este folosit pentru a crea librării noi pentru partea de client-side.
Câteva beneficile pe care le aduce în realizarea următoarelor sarcini:
Accesarea elementelor dintr-un document: mecanism complex de selectori, bazat pe structura CSS-ului (Cascading Style Sheets), îmbunătățind modul de accesare a unui element:
Exemple de selectori:
$(‘div’) – selectează toate tag-uri div ale paginii
$(‘#container’) – selectează elementul cu id container
$(‘.btn’) – selectează toate elementele care au clasa btn
$(‘ul li:nth-child(2)’ ) – selectează al n-lea copil al părintelui
$(‘div:visible’) – selectează toate tag-urile div care sunt vizibile
$(‘div’) .find(‘span’) – selectează toate span-urile care sunt conținute de un tag div.
Lista poate să continue, datorită modului cum se pot combina acești selectori, pentru a accesa elemente specifice : $(select.dropdown).find(':selected'); – selectează opțiunea aleasă dintr-un select cu clasa dropdown
Modificarea aspectului paginii: jQuery-ul oferă programatorilor o modalitate de a modifica aspectul grafic, de a anima elementele și de a le schimba starea.
Exemple de modificatori de aspect vizual:
$(‘div’).addClass(‘custom_class’) – adaugă clasa custom_class tuturor tag-urile div
$(‘#container’).css(‘width’) – oferă informatii despre latimea obiectului selectat
$(‘#container’).css(‘width’,’800’) – setează lățimea la 800 de px
$(‘.btn’) .toggleClass(‘btn-primary’) – adaugă sau sterge, una sau mai multe clase, in functie de clasele existene.
$(‘#aside’).scrollTop(300) – setează valoarea pozitiei pe verticala a elementului cu id aside
$(‘#aside’).hide() – ascunde elementul selectat
$(‘#aside’).slideDown() – afișează elementul cu un efect de tranziție
Gestionarea conținutului unui document: acest framework oferă un set de metode care permit schimbare oricărui aspect al conținutului, inclusiv întreagă structură HTML, dar si metode specializate pentru obtinerea informațiilor despre conținutul elementelor.
Exemple de modificatori de conținut:
$(‘#input_search’).val() – oferă valoarea atribuită elementului selectat
$(‘#input_search’).val(30) – schimbă valoarea atribuită elementului selectat
$(‘div’).html() – oferă conținutul HTML al tuturor elementelor selectate
$(‘div’).append() – adaugă conținutul HTML la sfârșitul fiecărui element selectat
Gestionarea actiuniilor unui utilizator : jQuery oferă un mod elegant de a gestiona o serie de evenimente pe care le poate desfășura un utilizator
Exemple de interceptori:
$(‘#input_search’).on(‘change’, function() { alert(‘valorea input-ului s-a modificat’);
});
$(‘.btn’).click(function(event)
{ var selected = event.currentTarget;
..
});
Una dintre cele mai mari schimbări, din ultimii 10 ani, în domeniul dezvoltării web a fost AJAX-ul (Asynchronous JavaScript and XML). Ajax-ul permite dezvoltatorilor să trimită și să primească date de la server, asincron, fără a afecta sau interacționară cu user experience (UX = perceptia unui utilizator asuprea un anumit produs), mai exact fără necesitatea reîncărcării paginii la o anumită acțiune.
Înainte de a intra în detaliile AJAX-ului, trebuie să explicăm tipul datelor care se utilizează, mai exact JSON (JavaScript Object Notation). JSON-ul este un format de interschimbare a datelor dintre partea client-side cu partea server-side, și invers. JSON-ul este construit pe două structuri:
O colecție de perechi nume – valoare
Listă ordonată de valori
Exemplu, JSON-ul trimis de api-ul de la Linkedin, atunci când se cere profilul complet al unui utilizator:
{
"certifications": {
"_total": 2,
"values": [
{
"id": 10,
"name": "Microsoft Specialist : Programming in HTML5 , CSS3 , Javascript"
},
{
"id": 45,
"name": "Oracle Database Design and Programming"
}
]
},
…
, "summary": "Technologies:\n■Web Apps (Asp.NET MVC, Javascript, JQuery, Bootstrap, SASS)\n■Database (SQL Server, Oracle)"
}
Pentru a transforma un obiect la tipul JSON, se folosește metoda:
JSON.stringify(obiect).
jQuery AJAX conține câteva din următoarele setări de configurare:
async : specifică dacă cererile AJAX sunt realizate asincron sau sincron, având valoare implicită setată pe true
contentType: setează tipul conținutului MIME a datelor trimise în cerere
data: datele trimise
dataType: specifică tipul de date primite ca răspuns, de la server. Valorile posibile sunt: HTML, XML, JSON sau script
error : permite specificarea unei funcții de gestionare a erorilor atunci când cererea a eșuat
succes : permite specificarea unei funcții de gestionare a datelor primite atunci când cererea s-a efectuat cu succes
timeout: specifică durata maximă în milisecunde, dacă o cerere durează mai mult decât această valoare, atunci această cerere este anulată
type: specifică tipul cererii, acesta poate fi : GET, POST, PUT sau DELETE, tipul implicit fiind GET
url: specifică url-ul cererii
Exemplu de cerere facută prin jQuery AJAX, pentru ASP.NET MVC:
$.ajax({
url: "/Jobs/Apply",
data: JSON.stringify( {
jobId: this.options.jobId
}),
type : "POST";
contentType: "application/json",
dataType: "json",
success:function(data){
$( "#jobs_container").html(data.Html);
},
error:function(jqXHR,status,err){ alert(status);}
});
2.3. ASP.NET
Descrierea ASP.NET în conformitate cu documentația Microsoft:
ASP.NET este o platformă web care oferă toate serviciile de care aveți nevoie pentru a construi aplicații web enterprise bazate pe server. ASP.NET este construit pe .NET Framework, astfel toate caractericile .NET sunt disponibile pentru aplicații ASP.NET. Aplicațiile pot fi scrise în orice limbaj, care este compatibil cu Common Language Runtime (CLR), inclusiv Visual Basic și C #.
Pentru a crea aplicații web ASP.NET, puteți utiliza Visual Studio. Instrumentele și opțiunile din Visual Studio care sunt concepute pentru crearea de aplicații web sunt denumite colectiv ca Visual Web Developer. În plus, produsul standalone Visual Web Developer Express este disponibil gratuit, care include un set de bază de caracteristici Web-design de la Visual Studio. “[Error: Reference source not found]
Există trei tehnologii pentru construirea de aplicații web în ASP.NET:
ASP.NET Web Forms (modelul original de programare)
ASP.NET Web Pages
ASP.NET MVC
ASP.NET include următoarele caracteristici:
O pagină și controale de framework
Compilatorul ASP.NET
Configurarea aplicației
Suport pentru debugging
Infrastructura de securitate
Monitorizarea performanței aplicației
Un framework de servici web XML, care mai târziu a fost înlocuit cu Windows Communication Foundation (WPF)
Management a ciclului de viață a aplicației
În următoarele pagini o să aprofundez ASP.NET Web Forms pe scurt și ASP.NET MVC în detaliu.
2.3.1. ASP.NET Web Forms
Scurt istoric
ASP.NET a fost o schimbare foarte mare atunci când a apărut pentru prima dată în 2002. Figura de mai jos ilustrează ce tehnologii oferea Microsoft în acel an.
Fig. 2.3-1 [8]
Cu Web Forms, Microsoft a încercat să ascundă atât HTTP cât și HTML (un limbaj care era necunoscut pentru multi dezvoltatori), prin modelarea interfaței cu utilizatorul (UI), ca o ierarhie de obiecte de control server-side. Fiecare control își păstra propria sa stare de-a lungul cererilor (folosind facilitatea ViewState), care se randează ca HTML atunci când era necesar și în mod automat conectarea evenimente de tip client-side (de exemplu, un eveniment de buton de click) cu partea server-side a evenimentului corespunzător.
Într-adevăr, Web Forms este un layer de abstractizare clasică, proiectat pentru a oferi o interfață grafică de utilizator (GUI) event-driven pe Web.
Puncte slabe
Dezvoltarea tradițională a ASP.NET Web Forms a fost importantă, în principiu, dar în realitate s-a dovedit mult mai complicată:
Spațiul de memorie a ViewState-ului: mecanismul de întreținere a stării de-a lungul cererilor (cunoscut ca și ViewState) constă în trimitea de date mari între client și server. Deoarece prin acest mecanism se face comunicarea între client-server și server-client, datele stocate pot ajunge la sute de kilobytes chiar și în cele mai simpliste aplicații, ceea ce duce la un timp de răspuns mai lent și creșterea lățimii benzii de internet a serverului.
Ciclu de viață a paginii: mecanismul de conectare a evenimentelor de pe partea de client cu cele de pe server care tratează codul poate fi extrem de dificil și delicat, deoarece puțini dezvoltatori au manipulat cu succes ierarhia de control la execuție, fără să aibă erori de ViewState sau faptul că anumite evenimente nu s-ar execută datorită pierderii handle-urilor.
Code-behind: Modelul code-behind a ASP.NET Web Forms oferă posibilitatea separării codului specific HTML într-o clasă de tip code-behind. Acest lucru a fost apreciat de un număr de dezvoltatori pentru separarea între logică și prezentare, dar acest lucru a dus la încurajarea programatorilor să amestece codul de prezentare cu logica aplicației în clase code-behind mari ca volum de linii de cod
(Clasa code-Behind: include codul de handle a evenimentelor și a interacțiunii dintre toate elementele paginii, această clasă este inclusă într-un fișier cu extensia asociată aspx.cs pentru C# sau aspx.vb pentru VB.NET).
Control limitat asupra HTML-ului: Controalele server (User controls, Web Server Controls, Web Parts Controls) pot duce la o randare HTML nedorită. În primele versiunii de ASP.NET, HTML obținut nu era în concordanță cu standardele Web sau cu buna aplicare a Cascading Style Sheets (CSS), și controalele server generau ID complexe și imprevizibile, care erau greu de accesat cu ajutorul Javascriptului. Aceste probleme au mai fost remediate în ultimii ani, dar încă poate fi dificit de a obține HTML așa cum dorești.
Abstractie permeabila: Web Forms`ul încearcă să ascundă HTML și HTTP ori de cât ori este posibil. Pe măsură ce dorești să implementezi comportamente custom, de obicei nu reușești să abstractizezi, fapt ce te obligă să modifici comportamentul
postback-ului sau diferite comportamente mai puțin obișnuite pentru a obține HTML-ul dorit. În plus, abstractizarea poate acționa care o barieră frustrantă pentru dezvoltatori Web competenți.
Greu de testat: Datorită faptului că este o arhitectură strâns cuplată, aceasta nu este recomandată pentru testarea automată, care a devenit un element esențial al dezvoltării software.
2.3.2. ASP.NET MVC Generalități
ASP.NET MVC este un cadru de dezvoltare web de la Microsoft, care combină eficiența și ordinea arhitecturii model-view-controller (MVC), cele mai recente idei și tehnici de dezvoltare agile, și cele mai bune părți existente ale platformei ASP.NET. Este o alternativă completă a tradiționalului ASP.NET Web Forms, oferind avantaje pentru proiecte de dezvoltare web.
Modelul MVC
Model-View-Controller (MVC), a fost un model arhitectural important în informatică pentru mulți ani, și va continua să fie. Numele inițial, apărut în anul 1979, a fost Thing-Model-View-Editor, modificat ulterior în Model-View-Controller.
MVC-ul separă interfața utilizatorul (UI) a unei aplicații în trei aspecte principale:
Model: un set de clase care descriu datele cu care lucrezi, împreună cu regulile business care descriu modul cum v-or fi modificate și manipulate
View: definește cum interfața utilizatorului (UI-ul) aplicației va fi randată
Controller: un set de clase care se ocupă de date primite de la utilizator, de fluxul complet al aplicației, cât și de logica specifică aplicației.
Fig. 2.3-2 [9]
Acest șablon de programare a căpătat o popularitate enormă ca model pentru aplicații web, pentru următoarele motive:
Interacțiunea utilizatorului cu o aplicație MVC urmează un ciclu natural: utilizatorul realizează o acțiune și aplicația își schimbă modelul de date și actualizează interfața în concordanță cu acțiunea utilizatorului. Acest ciclu se repetă ori de câte ori este nevoie. Aplicațiile Web comunică printr-o serie de cereri și răspunsuri HTTP.
Aplicațiile web necesită combinarea a mai multor tehnologii (cod executabil, bază de date, HTML, etc. ) și împărțirea lor pe mai multe seturi de tier-uri sau layere, șablonul MVC unificându-le într-un mod optim și natural.
Modelul MVC în Framework-uri Web
Model MVC este des folosit în programarea web. În contextul ASP.NET MVC, este definit specific astfel:
Models(Modele): aceaste clase încapsulează datele de la o bază de date, precum și cod folosit pentru manipularea datelor și aplicarea businessului logic. În contextul ASP.NET MVC, acestea sunt cel mai probabil Data Access Layer, utilizând un tool ca Entity Framework combinat cu cod particularizat care conține logica specifică.
View: Șablon de generare dinamică HTML.
Controller: aceasta este o clasă specială care gestionează relația dintre Model și View. Interacționează cu date introduse de utilizator, comunică cu Model-ul și decide ce View să randeze. În ASP.NET MVC, această clasă este convențional sufixată cu Controller.
Evoluția ASP.NET MVC
Prima oară a fost lansat ASP.NET MVC 1 în martie 2009, de atunci au aparut cinci versiuni imporante și altele intermediare.
ASP.NET MVC 1
Acest concept a fost schițat pentru prima oară în februarie 2007, de Scott Guthrie. Lucrurile au evoluat destul de rapid, alături de Eilon Lipton și un grup de programatori aceștia au analizat și schimbat prototipuri, cod și idei. Au fost nouă variante open-source înainte de lansarea oficială din 13 Martie 2009, ciclul de dezvoltarea a fost extrem de interactiv, punând accent pe interacțiunea comunității de-a lungul procesului de dezvoltare.
ASP.NET MVC 2
Versiunea ASP.NET MVC 2 a fost lansată un an mai tarziu, în martie 2010. Facilități importante apărute în această variantă:
UI Helpers cu generare automată prin intermediul scaffolding-ului cu template-uri
personalizabile
Modele bazate pe atribute de validare atât pe partea de client, cât și pe server
HTML Helpers puternic tipizate (strongly typed)
Îmbunătățirea tool-urilor din Visual Studio
Suport pentru partiționare aplicațiilor mari în Areas
Suport asincron a Controller-ului.
Suport pentru randarea unei subsecțiuni dintr-o pagină / site folosind Html.RenderAction
ASP.NET MVC 3
Versiunea ASP.NET MVC 3 a fost lansată la doar 10 luni de la MVC 2, impusă de lansarea Web Matrix-ului. Facilități importante apărute în această variantă:
Motorul de randare HTML (Razor)
Suport pentru .NET 4 Data Annotations (atribute aplicate proprietăților unei clase, pentru validare)
Îmbunătățirea validării modelului
Control mai mare și mai flexibil, cu suport pentru rezoluții dependente
Suport îmbunătățit a JavaScript-ului cu unobtrusive JavaScript, jQuery Validation, și JSON binding
Utilizarea NuGet-ului pentru a oferi acces la proiecte open-source sau tool-urilor Microsoft și gestionarea dependințelor de-a lungul aplicației
ASP.NET MVC 4
Versiunea ASP.NET MVC 4 a fost lansată pe data de 15 August 2012, în versiune finală. Aceasta versiune este construită pe baza celor mai importante caracteristici ale versiunilor anterioare și include noi caracteristici cum ar fi:
ASP.NET API Web, un nou framework pentru construirea HTTP și a serviciilor RESTful.
Un nou template HTML5-based și un nou template de proiect pentru aplicații mobile în cadrul produsului Visual Studio.
Selectare automată a randării View-urilor cu Display Modes. Acest feature este foarte util atunci când dezvolți aplicații care nu rulează doar pe browsere desktop, dar și pe browsere mobil, determinând automat ce View să randeze în funcție de browserul în care se face un request (acțiune).
jQuery Mobile și caracteristici pentru dezvoltarea aplicațiilor pentru browsere mobile.
Suport Task pentru controlere asincrone.
Suport Microsoft Windows Azure SDK pentru implementarea aplicațiilor hostate pe Windows Azure.
Bundling și minification pentru CSS și JavaScript a fișierelor, pentru a ajuta la reducerea numărului și mărimii cererilor HTTP. (elimineara spațiilor și renumirea variabilelor, numelor funcțiilor, etc pentru a scurta dimensiunea liniilor de cod)
Facebook, OpenID, și autentificarea OAuth.
ASP.NET MVC 5
Versiunea ASP.NET MVC 5 este ultima versiunea disponibilă în acest moment, data oficial de lansare a fost 17 Octombrie 2013 și update-ul cel mai de actualitate este 5.1.2 lansat pe data de 4 Aprilie 2014. Această vine alături de update-ul .NET Framework-ului versiunea 4.5.1.
Această versiune este un upgrade relativ minor și conține o multitudine de modificare aduse modului în care proiectele ASP.NET sunt create și gestionate în Visual Studio (Versiunea cea mai de actualitate a Visual Studio-ului este 2013 Update 2).
Noile caracteristice aduse Framework-ului MVC:
Filtre de autentificare: un nou tip de filtru, care poate fi folosit pentru a include diferite tipuri de autentificare în același Controller.
Suprascrierea filtrelor: un nou tip de filtru care se poate aplicate metodelor de tip acțiune folosit pentru a preveni filtre, definite global sau în același Controller, să se mai aplice.
Atribut pentru rutare(Attribute Routing): un set de atribute care permit rutelor URL să fie definite în interiorul clasei Controller-ului.
Bootstrap: cel mai popular framework de front-end pentru a dezvolta aplicații responsive.
ASP.NET Identity: proiectele MVC au fost updatate să folosească ASP.NET Identity pentru autentificarea și managementul identității unui utilizator.
2.3.3. ASP.NET MVC 5
Crearea unei aplicații în Visual Studio
Pentru a crea un nou proiect, trebuie să avem instalat Visual Studio 2013, produs oferit de Microsoft. După deschiderea aplicației Visual Studio, alegem FILE ➤ New Project.
Fig. 2.3-3
Această opțiune, deschide o nouă fereastră New Project care este împărțită în patru secțiunii. Panoul de navigație din stânga vă permite să navigați prin template-uri de proiect disponibile, fie on-line sau instalate. Alegem Templates, această acțiune va extinde panoul de navigare. Se vor afișa un set de template-uri de proiect instalate care sunt preconfigurate. Extindem nodul Visual C#, apoi nodul Web și selectam ASP.NET Web Application, utilizând .NET Framework 4.5.1 pentru a avea acces la noile caracteristici ale MVC 5-ului.
Fig. 2.3-4
Pentru acest tip de proiect avem la dispoziție mai multe template-uri, dar pentru această prezentare vom alege template-ul Empty, având selectat la core reference MVC.
Fig. 2.3-5
Conținutul suplimentar pe care îl oferă template-ul MVC include unele Controllere și View-uri standard, o configurație de securitate, câteva pachete populare de JavaScript și CSS (cum ar fi jQuery și Bootstrap), și un aspect care utilizează Bootstrap pentru a oferi o temă vizuală pentru conținutul aplicație. Template-ul Empty conține doare referințele de bază necesare unui mediu de dezvoltare pe arhitectură MVC și o structură de foldere simplistă.
Fig. 2.3-6
Descriere fisierelor/folderelor :
/App_Data : în acest folder se pun datele private, cum ar fi fișiere XML sau baza de date dacă utilizați SQL Express, SQLite, etc.
/App_Start: acest folder conține unele setări de configurarea de bază ale aplicației, inclusiv definirea rutelor URL, filtre și bundles.
/Areas: Areas sunt un mod de partiționare a unei aplicații mari în secțiuni mai mici.
/Content: Aici se păstrează conținutul static al fișierelor CSS și al imaginilor.
/Controllers: Folderul care conține clasele Controller.
/Models: Folderul în care se păstrează clasele de tip View (clase care se trimit la View pentru randare dinamică, pot conține atribute specifice pentru validări) sau Domain (clase care se utilizează pentru reține informația unui obiect din bază de date, aceastea nu conțin atribute de validări, în general sunt create a manipulă date într-un mod nativ C#).
/Scripts: Acest director este destinat pentru a păstra bibliotecile JavaScript necesare aplicației.
/Views: Acest director conține Layout-ul (HTML comun tuturor View-urilor, fiind adăugat în subfolderul comun aplicatiei /Shared), View-urile sau Parțial View-urile, grupate de obicei în foldere denumite după Controller-ul cu care sunt asociate.
/Global.asax: Acest fișier este clasă globală ASP.NET a aplicației. În interiorul clasei code-behind (Global.asax.cs), se vor seta configurația rutelor, precum și orice cod pentru a rula la inițializare sau la închidere, sau când apar excepții netratate.
Diagrama ciclului de viață al unei aplicați
Schema în detaliu a ciclului de viață a unei aplicații MVC, pentru a putea înțelege cele mai importante etape prin care fiecare aplicație MVC trece în cursul procesării unei cereri.
Fig. 2.3-7 [11]
Convenții ASP.NET MVC
Aplicațiile ASP.NET MVC, în mod implicit, se bazează foarte mult pe convenții. Acest lucru permite dezvoltatorilor să evite configurarea și specificarea anumitor lucruri care pot intra în conflict cu convenția impusă. De exemplu, MVC-ul folosește un director structurat pe denumire atunci când se dorește a se defini noi template-uri de View și această convenție va permite să omiți calea fișierului atunci când faci referire la un View în clasa Controller. În mod implicit, ASP.NET MVC caută fișierul View specific Controller-ului [ControllerName], în directorul \Views\[ControllerName]\. MVC-ul este conceput în jurul unor convenții sensibile care pot fi suprascrise dacă este necesar. Acest concept este denumit, în mod general, “convention over configuration”.
2.3.4. Controllers
În conceptul arhitecturii MVC, controller-ul procesează cererile HTTP primite de la utilizatori, fiecare cerere fiind tratată de controller-ul specific. ASP.NET MVC a implementat acest concept cu clasele .NET, care au metode de a procesa astfel de cereri.
Metodele din interiorul controller-ului sunt numite action methods, deoarece ele returnează un obiect de tip ActionResult. O cerere este procesată mai întâi de un routing engine (motor de rutare), care atribuie cererea unui controller și unei metode. După aceea, metodă este executată și se produce un action result, care este returnat utilizatorului. Cel mai comun obiect de tip de action result este View-ul care randeaza HTML în browser. Controller-ul poate avea comportament specific, numit filtru, prin adăugarea, clasei Controller sau action method-urilor, unui atribut în codul sursă(de exemplu, autentificarea).
În mod implicit, la crearea unui proiect se adaugă două reguli de rutare astfel încât aplicația să poată să ruleze.
Fig. 2.3-8
Prima regulă de rutare indică routing engine-ului să ignore cererile cu extensia resurselor cu extensia .axd, cum ar fi ScriptResource.axd și WebResource.axd. Aceste resurse nu există fizic în aplicație, sunt mai degrabă HttpModules care încarcă conținut special (cum ar fi imagini, script-uri, CSS, etc.) care sunt încorporate în fișierele DLL, fiind trimise la browser, ca parte a response-ului(răspunsului).
Pentru a funcționa corect, rutele trebuie să fie create atunci când aplicația pornește. Acest lucru se realizează prin metodă Application_Start definită în Global.asax.cs. În ASP.NET MVC 5, cât și în versiunile anterioare care suportă acest lucru, metoda Application_Start apelează metoda statică RegisterRoutes() din cadrul clasei RouteConfig, localizată în directorul /App_start, care adaugă rutele definite.
Înregistrarea rutei se face prin intermediul metodei MapRoute, a clasei RouteCollection. Definiția rutei conține numele rutei, acesta fiind primul parametru, al doilea parametru este pattern-ul rutei, iar al treilea parametru reprezintă valorile implicite ale elementelor din pattern-ul rutei.
Pattern-ul rutei identifică numele controller-ului prin intermediul proprietății controller, numele acțiunii prin intermediul proprietății action și informații suplimentare prin intermediul celui de-al treilea parametru care pot fi folosite în cadrul metodei. De reținut faptul că cererea HTTP nu include cuvântul “controller” chiar dacă numele Controller-ului conține acest sufix. De exemplu, cererea http://localhost/Home/About, ruta indică controller-ul HomeController și action method-ul About.
Exemple de cereri HTTP:
http://localhost/ (Controller=Home, Action=Index)
http://localhost/Home (Controller=Home, Action= Index)
http://localhost/Home/About (Controller=Home, Action= About)
http://localhost/Home/About/1 (Controller=Home, Action=Index, id =1)
Descrierea în detaliu a unui controller
Un controller poate fi creat prin folosirea caracteristilor care le oferă Framework-ul MVC Microsoft , lucru obținut prin derivarea controller-elor din clasa System.Web.Mvc.Controller.
System.Web.Mvc.Controller este o clasă care oferă suport pentru manipularea cererilor HTTP, într-un mod familiar majorității dezvoltatorilor MVC. Clasa Controller
oferă trei caracteristici cheie:
Action methods: Comportamentul unui controller este împărțit în mai multe metode, fiecare metodă corespunde unui URL diferit și este invocată cu parametrii din cererea HTTP.
Action results: Returnarea unui obiect care descrie rezultatul unei acțiuni (de exemplu, randarea unui View, redirecționarea la un URL diferit sau action method), care este apoi executat. Diferența din specificarea acțiunii și executarea ei, ajută la simplificarea testării.
Filters(filtre): puteți încapsula comportamente reutilizabile (de exemplu, autentificarea) în calitate de filtre, adăugând unui controller, controller-e sau action method-elor a unui atribut în codul sursă.
Clasa ActionResult este o clasă de bază pentru specificarea implementării unui action result. Următoarele clase derivă din clasă ActionResult și pot fi folosite ca tip de returnare a unui action method:
ViewResult: utilizat pentru a returna un view care să randeze HTML în browser. Acesta este cel mai des folosit tip de ActionResult.
PartialViewResult: similar ViewResult-ului, acesta returnează un view parțial (parțial view)
ContentResult: folosit pentru a return orice tip de conținut.
EmptyResult: care este echivalentul unei metode void
FileResult: folosit pentru a returna un conținut binar, de exemplu download-ul unui fișier.
HttpUnauthorizedResult: se folosește atunci când o cerere HTTP încearcă să acceseze conținut restricționat, de exemplu, informații care nu sunt disponibile unui utilizator care nu este autentificat și browser-ul îl va redirecta către pagină de login.
JavaScriptResult: returnează cod JavaScript.
JsonResult: returneaza un obiect în format JavaScript Object Notation (JSON).
RedirectResult: folosit pentru a efectua o redirecționare HTTP către un alt URL, fie temporar, fie permanent.
RedirectToRouteResult: folosit pentru a efectua o redirecționare HTTP, dar către o rutare specifică, diferit de un URL.
Cel mai bun mod de a crea un controller este prin derivarea sa din clasa Controller. Visual Studio realizează acest lucru, prin acțiunea de Add, selectând opțiunea Controller.
Fig. 2.3-9
Framework-ul MVC 5 ne oferă mai multe posibilități pentru crearea unui controller, inclusiv prin metoda scaffold, prin care se oferă posibilitatea generării unui controller întreg, cu metodele de bază (listare,creare, detalii, modificare și ștergere) a unui model corespunzător unei tabel din baza de date. Dar pentru această prezentare, am să prezint cum se generează un controller simplist, fără metode autogenerate, folosind MVC 5 Controller – Empty.
Fig. 2.3-10
Clasa Controller este conectată cu sistem Razor de randare a view-urilor, asfel
obiectele ViewBag sau ViewData, sunt comune view-ului și putem transmite date, pe care apoi să le afișăm.
Pentru a putea avea un conținut vizual în browser, trebuie să navigăm la directorul /Views/[NumeController] ( sau într-un alt director) și să adăugam un View cu ajutorul interfeței Visual Studio-ului, astfel:
Fig. 2.3-11
Alegem numele View-ului și template-ul său (observăm posibilitatea de a genera automat prin scaffold). Pentru aceasta prezentare, am folosit Empty(without model) pentru a genera un view simplist. Pentru a arăta legătura dintre Controller și View, prin intermediul engine-ul Razor, o să setăm în obiectul ViewBag, o proprietate dinamică, care să o afișăm în browser.
Codul din clasa DefaultController:
using System.Web.Mvc;
namespace WebApplication1.Controllers
{
public class DefaultController : Controller
{
// GET: Default
public ActionResult Index()
{
ViewBag.MessageFromController = "Hello from my Controller";
return View();
}
}
}
Codul din View-ul Index, din directorul /Views/Default/:
@{
ViewBag.Title = "View";
Layout = null;
}
<h2>View – @ViewBag.MessageFromController</h2>
Vizualizare în browser a codul descris:
Fig. 2.3-12
Controller-ul necesită, în mod frecvent, accesul datelor de la cererile primite, cum ar fi valorile query string-ului(valorile ce contruiesc URL-ul, identificate sub forma aceasta: “?nume_var=valoare_var&nume_var1=valoare_var1&…” ), valorile unui formular și valorile parsate de la URL de engine-ul de rutare.
Sunt trei modalități prin care datele se accesează:
Extragerea de la un set de obiecte specifice contextului(Request.QueryString, Request.Form, Request.Url, RouteData.Route, HttpContext.Session, etc.)
Maparea datelor ca parametri către action method.
Invocarea implicită a caracteristicii de model binding a framework-ului.( această caracteristică va fi explicată în subcapitolul Models)
Atunci când se crează un controller moștenit din clasa Controller, se oferă accesul la proprietăți care accesează informații despre cererea HTTP. Aceste proprietăți includ: RouteData, Request, Response, HttpContext și Server. Fiecare proprietate oferă informații diferite despre cererea făcută. Câteva exemple:
Request.QueryString: variabilele GET care sunt trimise în cerere(de exemplu, informațiile URL-ului).
Request.Form: variabilele POST care sunt trimise în cerere(de exemplu, datele trimise de la un formular).
Request.Url: URL-ul cerut.
RouteData.Values: parametrii activi ai rutării(fie extrași din URL sau valorile implicite din regula de rutare).
HttpContext.Application: chache-ul aplicației.
TempData: date temporale stocate pentru utilizatorul curent.
Action method-urile pot primi parametrii, printr-un mod elegant care nu mai necesită extragerea manuală a parametrilor din obiectele specifice contextului (context objects). De exemplu:
Fig. 2.3-13
În concluzie, controller-ele sunt clasele care gestionează cererile utilizatorului. Acestea sunt responsabile de interacțiunea dintre user și server, de răspunsul primit de utilizator și de manipularea datelor primite. Sunt moștenite din clasa System.Web.Mvc.Controller a framework-ului MVC. Cererile sunt trimise prin protocolul HTTP și sunt interpretate de routing engine, care decide ce acțiune s-a apelat. Metodele din interiorul clasei controller-ului se numesc action methods deoarece returnează obiecte de tip ActionResult, cel mai comun fiind ViewResult-ului care returnează HTML browser-ului.
2.3.5. Views
Unul dintre cele mai frecvente tipuri de action result este ViewResult. Acest tip indică faptul că ASP.NET MVC ar trebuie să invoce o pagină view pentru a afișa HTML în browserul utilizatorului. View-ul este responsabil pentru producerea interfeței utilizatorului. Aceasta primește referința model-ului (informația care trebuie Controller-ul să o afișeze), și view-ului transformă acest model în conținut HTML.
View-urile include adeseara cod server-side pentru a produce HTML dinamic. Când se crează un view, view engine-ul implicit utilizat este engine-ul Razor în ASP.NET MVC 5, care este un nou view engine nou, simplist și cu o sintaxa fluidă, comparativ cu view engine-ul ASPX-ului care are o sintaxa asemănătoare cu cea a Web Forms-ului.
Diferențe între view engine-urile ASPX și Razor:
Fig. 2.3-14
View-urile se stochează în directorul /Views din interiorul aplicației, structurate după convențiile ASP.NET MVC-ului. Prin convenție, directorul /Views conține un dosar pentru fiecare controller, cu același nume că și al controller-ului, dar fără sufixul Controller. Astfel, pentru HomeController, există un dosar în directorul /Views numit Home. În interiorul fiecărui dosar asociat unui controller, există un view pentru fiecare metodă din controller, având același nume. Acest lucru indică faptul că fiecare view este asociat unei action method din controller. De exemplu:
Fig. 2.3-16
Fig. 2.3-15
Observăm că acțiunea controller-ului nu specifică numele view-ului, fapt ce indică că se aplică o convenție pentru a localiza fișierul view. Se caută view-ul care are același nume, ca și acțiunea din directorul /Views/Home, fiind selectat în acest caz /Views/Home/Index.cshtml.
Această convenție poate fi anulată, în cazul în care se dorește a se executa un view diferit, prin specificarea unui nume diferit de view (din același director)
Fig. 2.3-17
sau calea completă a unui view care se află în alt director (trebuie specificată extensia view-ului, de exemplu .cshtml pentru view-urile Razor care contin cod C#) .
Fig. 2.3-18
Un mod de a transmite informații din controller către view, se poate face prin ViewData. Se pot seta sau citi valori, folosind sintaxa standard de dictionary class, astfel: ViewData["ProductName"] = "Product1";
Un alt mod de transmitere, introdus în ASP.NET MVC, este ViewBag-ului, care este o implementare dinamică peste ViewData, care ne permite să setăm informații astfel: ViewBag.ProductName = "Product1";
Deși am prezentat două moduri de trimitere a informației la un controller, cel mai bun și cel mai eficient mod este prin crearea unor strongly type view este prin includerea detaliilor unui obiect view model, precum în exemplul de mai jos:
Fig. 2.3-19
Specificarea view model-ului se face folosind cuvântul cheie model din framework-ul Razor. Specificarea tipului modelului se face prin m cu minuscule și M cu majuscule, atunci când se citește o valoare. Acest lucru ajută la aspectul codului, dar și pentru suportul IntelliSense pe care îl oferă Visual Studio.
Proprietatea Layout este o caracteristică a Razor-ului, care permite să definești un view comun, care va fi folosit ca un șablon pentru conținut vizual ( Similar MasterPage-ului din ASPX view engine), definit standard în fișierul _ViewStart.cshtml, din cadrul directorului /Views pentru a se aplica la toate view-urile fără a mai fi nevoie să fie specificat layout-ul, astfel:
Fig. 2.3-20
Conținutul fisierului /View/Shared/_Layout :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title – My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
@Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav"
<li>@Html.ActionLink("Home", "Index", "Home")</li> <li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact","Contact", "Home")</li>
</ul>
@Html.Partial("_LoginPartial")
</div>
</div>
</div>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>© @DateTime.Now.Year – My ASP.NET Application</p>
</footer>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body></html>
Cele două comenzi din interiorul tag-ului <head> a paginii: @Scripts.Render() și @Styles.Render(), sunt utilizate pentru a referințe externe către fișiere Javascript, respectiv CSS, codul produs trece prin procesul bundle (proces de unificare a mai multor fișiere într-un singur fișier) și este afișat în pagină astfel:
<link href="/Content/css?v=…" rel="stylesheet">
<script src="/bundles/modernizr?v=…"></script>
Deasemea, se pot defini secțiuni de cod HTML, care vor fi stocate în pagina de layout prin sintaxa: @RenderSection("nume_sectiune") , astfel fiecare view va fi obligat să definească conținutul secțiunii, dacă nu e specificat printr-un parametru adițional. Pentru a defini o secțiune care nu este obligatorie : @RenderSection("nume_sectiune", required : false).
Metoda @RenderBody()generează conținutul principal de la o pagină view.
Metoda @Html.ActionLink("Home", "Index", "Home") este un utilitar HTML, scopul lor fiind de a îngloba funcționalități în bibioteci pentru a genera cod HTML, care poate fi refolosit în toată aplicația web. Este doar un exemplu din suita de utilitare pe care le oferă ASP.NET MVC 5, dar se pot crea și utilitare personalizate. Acest utilitar generează un tag <a> html, construit pe baza următorilor parametri:
Textul primit va fi afișat în tagul <a>
Numele action method-ul
Numele controller-ului pentru care se apelează action method-ul
Codul HTML generat fiind : <a href="/Home/Index">Home</a>.
2.3.6. Models
Acest capitol descrie ultimul element al arhitecturii MVC: models.
În contextul arhitecturii MVC, cele mai relevante tipuri de model sunt:
Data Model: specific interacționării cu baza de date
Business Model: folosit pentru manipularea datelor, operații de validare, folosit pentru business logic, interacționând cu data model pentru obținea și salvarea datelor
View Model: specific pentru trimiterea informațiilor de la controller la view, conține validări prin intermediul atributelor disponibile
Exemplu de data model: Acest model a fost generat folosind un framework (ORM), numit Entity Framework. Ideea principală a funcționării acestui utilitar este de a crea un mecanism de mapare a bazei de date, care oferă posibilitatea dezvoltatorilor să lucreze cu limbaje orientate pe obiecte (C#) pentru a manipula tabele, view-uri ( view specific sql), proceduri stocate. Rezultatul folosirii unui ORM( object-relațional mapping)
este un set de clase care implementează Fig. 2.3-21
funcționalitățile de citire, creare, modificare și ștergerea a unor informații dintr-o bază de date. O caracteristică puternică a Entity Framework-ului este posibilitatea de a putea merge pe relaționările unui tabel, definite prin foreign key în baza de date, direct prin numele tabelei cu care este relaționat. De exemplu:
File file = db.Files.
Where(x => x.Id_User !=null).FirstOrDefault();
long userId = file.User.Id;
Variabila userId va conține id user-ului relaționat cu acel fișier.
Entity framework suportă LINQ (Language-Integrated Query), folosit pentru a scrie query-uri folosind sintaxa limbajelor că C# sau Visual Basic. Exemplu:
Notația db face referință către clasa generată de Entity framework, pentru a putea accesa baza de date:
public JobProfilerEntities db = new JobProfilerEntities();
Exemplu de view model:
Scopul acestei clase este de a comunica date din controller în view, pentru fi randate că elemente HTML, folosind utilitare oferite de ASP.NET. Este un model mai complex, din punct de vedere al conținutului de metadate (“date despre date”). Singurul loc scop este de a oferi informații view-ului despre cum se vor genera datele. Fig. 2.3-22
Acesta clasă este folosită și pentru a primi informații în controller atunci când un utilizator trimite datele unui formular. Aici intervine conceptul de model binding.
Date trimise, folosind HTTP POST, sunt stocate în colecția HttpRequest.Form. Dacă modelul folosit la generarea view-ului unui formular, este de tipul view-model, adică este o clasă strongly typed, ASP.NET MVC-ul extrage automat informațiile din obiectul HttpRequest și le mapează corespunzător modelului folosint pentru a genera acel formular. Avantaje: conversia de tipuri de date este automată, nu mai este nevoie de cod manual pentru a extrage valorile trimise și datele pot fi ușor validate. Metoda de login al unui utilizator folosește modelul exemplificat mai sus, astfel pentru metoda de POST care se ocupă cu validarea și logarea unui utilizator, datele nu se vor mai extrage din HttpRequest, ci direct din modelul specificat ca parametru al metodei.
Exemplu:
Fig. 2.3-23
2.4. BForms
BForms este un proiect open-source care constă într-o colecție de:
Utilitare html spefice ASP.NET MVC-ului
module de JavaScript AMD
CSS personalizat cu SASS care extinde Twitter Bootstrap
Acest framework oferă o gamă mare de opțiuni în contruirea unei paginii web, timpul necesar pentru a dezvolta este mult mai mic datorită componentelor customizate (grid, panel, form, group editor, input-uri HTML5), validarea componentelor client-side și server-side, integrarea framework-ului open-source Twitter Bootstrap, module de JavaScript precum Select2 și Typeahead, încărcarea modulelor JavaScript prin intermediul RequireJs-ului care îmbunătățesc calitatea și efortul depus al JavaScript-ului scris, folosirea SASS-ul pentru a scrie cod de stilizare HTML într-un mod organizat, compilat apoi în CSS, și nu în ultimul rând, integrarea cu framework-ul .NET. BForms este diponibil atât pe GitHub (https://github.com/stefanprodan/bforms), cât și ca pachet de NuGet (http://www.nuget.org/packages/BForms.MVC/). Site-ul unde se pot găsi detalii suplimentare despre framework, inclusiv pașii de instalare, documentația și demo-uri:http://bforms.stefanprodan.eu/.
Echipa care contribuie la dezvoltarea acestui framework: https://github.com/stefanprodan/BForms/graphs/contributors, proiect la care contribui din anul 2013, luna noiembrie.
2.4.1 Helpere de HTML Razor
BForms-ul are implementat o suită de extensii HTML, care cuprinde toate tipurile de input-uri HTML5, input-uri de tip listă cu selecție simplă sau multiplă, input pentru data și timp, încărcare de fișiere, input cu autocompletare și o gamă de input-uri de interval. Pentru această prezentare am să prezint sumar caracteristicile, doar pe cele mai importante și utilizate în aplicația acestei lucrări.
Pentru a specifica comportamentul a unei proprietăți dintr-o clasă, se adaugă atributul BsControl(BsControlType.[TipInput]). Acest atribut specifică tipul proprietății și helper-ul de HTML îl generează în funcție de tipul definit. O altă modalitate de a genera input-uri BForms este prin folosirea helper-elor specializate fiecărui tip.
Atributul BsControlType este de tip enum (enumeratie, tip specific C#-ului) și cuprinde următoarele elemente:
TextBox , helper HTML: BsInputFor
TextArea, helper HTML: BsInputFor
Password, helper HTML: BsInputFor
Url, helper HTML: BsInputFor
Email, helper HTML: BsInputFor
Number, helper HTML: BsInputFor
NumberInline, helper HTML: BsInputFor
DatePicker, helper HTML: BsInputFor
TimePicker, helper HTML: BsInputFor
DateTimePicker, helper HTML: BsInputFor
CheckBox, helper HTML: BsInputFor
RadioButton, helper HTML: BsInputFor
ColorPicker, helper HTML: BsInputFor
Upload, helper HTML: BsInputFor
Autocomplete, helper HTML: BsSelectFor
DropdownList, helper HTML: BsSelectFor
DropdownListGrouped, helper HTML: BsSelectFor
ListBox, helper HTML: BsSelectFor
ListBoxGrouped, helper HTML: BsSelectFor
TagList, helper HTML: BsSelectFor
TagListGrouped, helper HTML: BsSelectFor
CheckBoxList, helper HTML: BsSelectFor
RadioButtonList, helper HTML: BsSelectFor
DatePickerRange, helper HTML: BsRangeFor
TimePickerRange, helper HTML: BsRangeFor
DateTimePickerRange, helper HTML: BsRangeFor
NumberRange, helper HTML: BsRangeFor
Proprietatea unui model poate fi decorată deasemenea cu atributul Display care poate avea următoarele proprietăți:
Name: denumirea care va fi afișată
Description: oferă informații suplimentare asupra proprietății din model
Promp: indică valoarea implicită afișată când input-ul este nesetat, similar proprietății placeholder a input-urilor din HTML
Atributele specifice validării din ASP.NET MVC se afișează cu ajutorul
helper-elor : BsValidationCssFor și BsValidationFor.
Exemplu:
Fig. 2.4-1
Model: Fig. 2.4-2
View:
Fig. 2.4-3
Utilitarul HTML BsSelectFor
Acest utilitar de HTML este specializat pentru a genera HTML folosit pentru liste cu selecție simplă sau multiplă, cu caracteristici diferite. Pentru a putea folosi o proprietate dintr-o clasă cu acest utilitar, tipul acesteia trebuie să fie BsSelectList<T>, unde <T> este tipul valorii selectate de utilizator. BsSelectList este o clasă extinsă peste clasă de bază SelectList a ASP.NET MVC-ului.
Fig. 2.4-4
Proprietatea Items, de tip List<BsSelectListItem> este folosită pentru a adaugă elementele care vor fi folosite și afișate în browser. Proprietatea SelectedValues este folosită pentru identifică elementele din proprietatea Items care sunt selectate. Tipul BsSelectListItem este derivat din clasa SelectListItem a ASP.NET MVC-ului.
Fig. 2.4-5
BsSelectListItem-ul cuprinde următoarele proprietăți:
Text, de tip sting, indică valoarea care este afișată utilizatorului
Value, de tip string, indică valoarea atribuită
Selected, de tip bool, indică dacă valoarea este selectată
Data, de tip Dictionary<string, string>, oferă informații suplimetare despre element
<option [data-key1="value1", …]></option>
GroupName și GroupKey, de tip string, folosite pentru a defini liste grupate pentru tipurile disponibile
Exemplu:
Fig. 2.4-6
Model:
Fig. 2.4-7
View:
Fig. 2.4-8
2.4.2 Componenta Panel
Componenta BsPanel a framework-ului este dezvoltată pentru a oferi o multitudine de funționalități, disponibilă atât pe desktop, cât și pe mediul mobil. Cel mai important aspect al acestei componente este modul de generare atât editabil (Editable), cât și specific doar pentru citire (Readonly) prin suportul AJAX al client-side-ului. Pentru a utiliza aceasta caracteristică, proprietatea clasei trebuie să fie decorată cu atributul: BsPanel.
Mod specific doar pentru citire(Readonly):
Fig. 2.4-9
Mod editabil(Editable):
Fig. 2.4-10
Un aspect important al acestei componente este faptul că permite împărțirea unei clasei model în subcomponente Panel, beneficiu care ajută la clasificarea informațiilor în subcomponente, astfel utilizatorul v-a putea modifica/vizualiza informații pe componente, îmbunătățind interacțiunea din utilizator și aplicație, într-un mod modular.
Pentru exemplificarea modului de folosirea a acestei componente, se vor folosi porțiuni din codul curent al aplicației care se prezintă în această lucrare, specifice profilului unui utilizator înregistrat.
Împărțirea pe categori a profilului:
Fig. 2.4-11
Modelul principal al profilului utilizatorului:
Fig. 2.4-12
Observăm trei proprietăți ale atribului BsPanel:
Id: setează un id unic
Expandable: setează starea expandării unui panel
Editable : specifică dacă panel-ul este editabil
View-ul profilului utilizatorului
X.1. Fig. 2.4-13
Pentru a genera o colecție de panel-uri, s-a utilizat utilitarul html: Html.BsPanelsFor(), specific acestui framework. Metode folosite:
ConfigurePanels(Action<BsGridRowConfigurator<TRow>>cfg): folosind acesta metodă se setează proprietățile specifice fiecărui panel
GetReadonlyUrl(string): specifică url-ul care se ocupă cu încărcarea formularului read-only
GetEditableUrl(string): specifică url-ul care se ocupă de încărcarea formularului de editare
UpdateUrl(string): specifica url-ul care se ocupă cu manipularea și salvarea datelor din formularul editabil
For<TValue>(Expression<Func<TModel, TValue>> expression): acest utilitar returnează un BsPanelHtmlBuilder folosit pentru a contrui și configura html-ul panel-ului.
Content(string): setează continutul html al panel-lui.
Glyphicon(Glyphicon glyphicon): setează glyphicon-ul panel-ului, caracteristica ca framework-ului client-side Bootstrap
Expanded(bool): setează starea de expandare a panel-ului
Mode(BsPanelMode mode): metodă folosită pentru a specifica dacă panel-ul este editabil sau doar disponibil pentru citire pentru a suprascrie orice setare existentă sau viitoare, se folosește enum-ul BsPanelMode (Readonly, Editable sau Both)
HtmlAttributes(Dictionary<string, object> htmlAttributes): atributele html specifice fiecărui panel.
Fig. 2.4-14
Codul Javascrip utilizat:
Fig. 2.4-15
5
Pentru inițializarea componentelor, trebuie să includem script-ul bforms-panel, disponibil că modul AMD JavaScript. Prin setarea atributelor html în partea de view, aceste panel-uri pot fi selectate folosind selectori jQuery și inițializați prin intermediul metodei: bsPanel(). Această metodă poate primi numeroase opțiuni și proprietăți, inclusiv trigger-i (declanșator de eveniment) definiți în fișierul bforms-panel.js din cadrul framework-ului. Pentru mai multe informații și exemple, vizitați site-ul oferit în documentație sau http://bforms.stefanprodan.eu/Docs/Panel#panel-section.
2.4.3 Componentele Grid și Toolbar
Componentele de Grid și Toolbar sunt componente complex dezvoltate pentru gestionarea datelor dintr-o bază de date, oferind suport pentru operați CRUD (Create, Read, Update, Delete) și căutări pe seturi de date complexe. Setul de caracteristici include:
Paginare prin AJAX ( fără reîncărcarea paginii)
Sortarea coloanelor
Filtrarea elementelor
Editarea elementelor
Logica de interogare a bazei de date poate fi personalizată
Interfață vizual prietenoasă, folosind Bootstrap v3
Suport touch pentru dispozitive mobile
Suportul acțiunilor asupra mai multor rânduri din tabel
Implementarea unui grid funcționabil se realizează ușor, datorită utilitarelor implementată pentru lucrul cu bază de date, prin intermediul Entity Framewok-ului
JavaScript cu un spectru mare de utilități, gestionări de evenimente și posibilitatea de a personaliza modul de funcționare a componentei.
Pașii de configurare și folosire acestor două componente se pot regăsi pe site-urile http://bforms.stefanprodan.com/Start/Grid, http://bforms.stefanprodan.eu/Docs/Toolbar
sau pe GitHub, unde se poate găsi codul sursă folosit pentru a implementa pagina demo a grid-ului și a toolbar-ului:https://github.com/stefanprodan/BForms , proiectul
BForms.Docs.
În următorii pașii, am să descriu, pe scurt, un modul din aplicația acestei lucrări, folosind componența de grid și toolbar. Modulul care va fi prezentat este modul de management a job-urilor dintr-o companie.
Fig. 2.4-16
Model-ul
Primul pas în crearea grid-ului este definirea clasei corespunzătoare unui rând.
Proprietățile care se vor afișa visual în grid trebuie să fie decorate cu un atribut specific framework-ulu:BsGridColumn, având următoarele proprietăți:
Width: specifică lățimea necesară care trebuie să fie aplicată. Datorită folosirii framework-ului Bootstrap, suma width-urilor tuturor proprietăților unei clase trebuie să fie egală cu 12, convenție impusă datorită folosirii sistemului lor de responsive (modul cum se afișează elementele pe diferite rezoluții ale device-ului care accesează). Fig. 2.4-17
IsSortable: specifică dacă acea coloană poate fi sortată
Se observă faptul că clasa JobRowModel este derivată din clasa BsGridRowModel<JobDetailsReadonly>. Această derivare este necesară pentru a specifica tipul model-ului care va generat în detaliile unui rând, caracteristică ce o voi exemplica în următoarele paginii, și datorită acestei derivării, trebuie să adăugăm metoda GetUniqueID() necesară pentru a specifică un id unic pentru fiecare rând.
Următorul pas necesar este definirea view-model-ului necesar pentru întreaga pagina:
Fig. 2.4-18
Acest model are două proprietăți definite:
Grid : reprezintă grid-ul paginii, însă pentru definirea lui este nevoie de două lucruri, de decorararea cu atributul BsGrid și tipul acestuia trebuie să fie BsGridModel<T>, unde <T> reprezintă modelul rândului, în acest caz fiind JobRowModel. Proprietatea HasDetails a atribului BsGrid specifică dacă rândurile pot avea detalii suplimentare.
Toolbar: reprezintă toolbar-ul paginii, decorat cu atributul BsToolBar și tipul acestei proprietăți să fie de forma:
Fig. 2.4-19
dar poate fi suprascris să accepte mai multe modele. Tipurile deja implementate reprezintă:
TSearch – tipul model-ului folosit pentru caracteristică de căutare avansată a toolbar-ului
TNew – tipul model-ului folosit pentru formularul de adăugare
TOrder – tipul model-ului folosit pentru ordonarea rândurilor, dacă este necesar.
View-ul
După ce am definit view-model-ul paginii, trebuie să specificăm utilitarele HTML în view pentru componența de Grid și ToolBar.
Pentru componenta de Grid, utilitarul HTML folosit pentru a randa un grid este: @Html.BsGridFor(m => m.Grid)
Acest utilitar are disponibile următoarele metode:
GridResetButton() : adaugă un buton pentru resetare
AllowAddIfEmpty() : afișarea butonului de adăugare în grid, când acesta nu conține informații
ConfigureColumns(Action<BsGridColumnFactory<TRow>> configurator): în mod implicit, grid-ul se va contrui pe bază proprietăților din atributul BsGridColumn setat în modelul de rând, dar prin aceasta metodă se pot adauga coloane noi, șterge coloane sau modifica informații (textul, atribute html, dacă este sortabil)
ConfigureRows(Action<BsGridRowConfigurator<TRow>> configurator):
folosind această metodă, pentru fiecare rând, se pot seta atribute html (.HtmlAttributes), culoare(.Highlighter), calea către view-ul de detalii suplimentare (.HtmlAttributes) sau dacă se poate selecta (.HasCheckbox)
ConfigureBulkActions(Action<BsBulkActionsFactory> configurator):
Declanșează randarea unui set de butoane deasupra grid-ului și pentru fiecare rând un checkbox (input HTML). Butoanele sunt folosite pentru a aplica anumite acțiuni pe rândurile selectate (activare, dezactivare, ștergere, refresh, etc).
PagerSettings(BsPagerSettings pagerSettings): această metodă este folosită pentru a configura setările paginatorului grid-ului, prin specificarea numărul de elemente per pagină, ascunderea sau afișarea unor butoane.
Fig. 2.4-20
Pentru componenta de Toolbar, utilitarul HTML folosit pentru a randa un grid este: @Html.BsToolbarFor(x => x.Toolbar)
Acest utilitar are disponibile următoarele metode:
DisplayName(string): textul care va fi afișat in interiorul toolbar-ului
HtmlAttributes(Dictionary<string, object> htmlAttributes): atributele html ale toolbarului.
ConfigureActions(Action<BsToolbarActionsFactory<TToolbar>> configurator): metodă folosită pentru a defini diferite acțiuni, butoane tip list sau tip link. Toolbar-ul are implementat standard patru funcționalități:
căutare rapidă, căutare avansată, adăugare și ordonare. Pentru aceste funcționalități, în afară de căutare rapidă, se poate seta un conținut vizual pentru o anumită proprietate din model prin intermediul metodei Tab(Func<TToolbar, MvcHtmlString> tabDelegate).
Fig. 2.4-21
Repository-ul
Repository-ul este un șablon construit pentru a evita duplicarea codului de access logic (modul cum se comunică cu baza de date, servicii, fișiere xml, etc). Acesta ascunde detaliile despre cum datele sunt citite, prelucrate și a modului de salvare a informațiilor modificate.
Pentru a simplifica lucrurile, a fost creată o clasă abstractă care standardizează partea server-site. Moștenind această clasă, de tip BsBaseGridRepository<TEntity, TRow>, unde TEntity este modelul entității și TRow este modelul rândului unui grid, trebuie implementate câteva metode pentru a putea folosi standardul repository-ului din BForms.
Metodele care trebuiesc implemetate, în ordinea executării lor:
Query() : obținerea datelor necesare
OrderQuery(IQueryable<TEntity> query): ordinează datele în funcție de proprietățile din orderedQueryBuilder
MapQuery(IQueryable<TEntity> query): metodă care se ocupă cu selectarea datelor și convertirea lor la modelul rândului
OrderQuery(TRow row): nu e obligatorie, dar este necesară dacă grid-ul are detalii suplimentare asupra rândurile, aceasta se ocupă cu citirea, prelucrarea și salvarea în proprietatea Details a row-ului ( rândul selectat)
Fig. 2.4-22
Controller-ul
Următorul pas, și final pe partea de server, este construirea grid-ului și trimiterea sa la view-ul corespunzător. Modul prin care se realizează acest lucru este prin apelarea metodei ToBsGridViewModel() în controller.
Fig. 2.4-23
Pentru paginare și obținerea datelor suplimentare a unui rând, trebuie să implemetăm două metode care se ocupă de aceste lucruri și trimiterea url-ului lor în partea de JavaScript. Pentru acest exemplu, datele se trimit către un obiect de tip dicționar prin intermediul metodei RequiredJsOptions, a framework-ului RequireJs.
Fig. 2.4-24
JavaScript-ul
Pentru a avea acces la metodele grid-ului și toolbar-ului, trebuie să includem bforms-grid și bforms-toolbar prin metoda de include a modulelor javascript, require a framework-ului RequireJs.
require([
'bforms-grid',
'bforms-toolbar'
], function () { }
Fig. 2.4-25
Pentru aplicarea script-ul de grid, se foloseste metoda $('#grid').bsGrid().
Opțiuni care au fost specificate în modulul de job-uri al acestei prezentări:
$toolbar : specifică toolbar-ul asociat grid-ului din DOM-ul paginii
uniqueName: identificator unic al grid-ului
pagerUrl : url-ul folosit pentru paginarea grid-ului
sortable : valoarea booleană care spefică dacă coloane sunt sortabile între ele
filterButtons: configurează selectori pentru acțiunile bulk
gridActions: configurează comportamentul acțiunilor bulk
detailsUrl: url-ul folosit pentru încărcarea conținutului suplimentar al unui rând, dacă acest comportament a fost specificat
beforeRowDetailsSuccess: funcție care se declanșează înainte de încărcarea conținutului suplimentar în pagină browser-ului, având acces la conținutul care va fi afișat
rowActions: reprezintă un set de acțiuni specifice selectării unui rând sau a mai multor rânduri
Pentru aplicarea script-ul de toolbar, se foloseste metoda
$('# toolbar ').bsToolbar(). Opțiunii care au fost specificate în modul de job-uri al acestei prezentării:
uniqueName: identificator unic al grid-ului
subscribers: specifică grid-ul sau grid-urile
cu care se relaționează pentru operațiunii de adăugare, căutare simplă, căutare avansată. Fig. 2.4-26
Printscreen care arată caracteristica de detalii suplimentare a unui rând:
Fig. 2.4-27
2.4.4 Componentele JavaScript
2.4.5 Componenta RequireJs
RequireJs este un utilitar folosit pentru încărcarea fișierelor JavaScript în mod asincron, datorita capacități de a încărca dependențe imbricate a modulelor JavaScript, fiind un instrument pentru optimizare, care ajută la procesul de dezvoltare și simplitatea modului de utilizare pentru dezvoltator.
Fișierul cu calea relativă: scripts/customJs/customJs-v2.js, este definit ca modul JavaScript, folosind RequireJs astfel:
require.config({
baseUrl: 'scripts',
paths: {
//În stânga, este identificatorul unic al //modulului
//În dreaptă, numele fișierului, relativ la //calea definită în baseUrl, fără a mai adaugă //extensia.js
customJs: ' customJs-v2’
}
});
Cel mai important beneficiu, care îl oferă acest utilitar, este încărcarea fișierelor JavaScript în mod asincron, șablon de dezvoltare numit Asynchronous Module Definition (AMD). Exemplu:
define(['jquery', 'select2'] , function ($, select2) {
return function () {
$('input').select2();
};
})
Modulul jquery și modulul select2 se vor încărca asincon, ca apoi în interiorul funcției principale să avem acces la metodele definite în jquery, select2, devenind referințe de care depinde modulul exemplificat mai sus.
2.4.6 Framework-ul Bootstrap
Acest framework este dezvoltat de compania Twitter. A devenit foarte popular în rândul dezvoltatorilor și a software-uri de dezvolare web datorită setului mare de utilități oferit. Câteva dintre cele mai imporantante caracteristici oferite:
Ușor de folosit: datorită modul de implementare și a documentației oferită pe site-ul http://getbootstrap.com/. Documentația cuprinde informații detaliate despre fiecare componentă, cu exemple de cod și imagini.
Responsive (se adaptează la orice dimensiune a browserului): datorită sistemului de grid pe 12 coloane definit pe patru tipuri de dimensiuni a rezoluției browserului :≥ 1200px (.col-lg-), ≥992px (.col-md-), ≥768px (.col-sm-) si < 768px (.col-xs-), posibilității de a ascunde sau afișa elemente în funcție de rezoluția browserului (clasă .hidden-xs specifică elementului să nu fie afișat pe rezoluția de telefon mobil) și redimensionării automate a elementelor, fie că sunt fixate sau fluide.
Stilizare de bază pentru majoritatea elementelor HTML: butoane, formulare, liste, tabele, imagini, tipografie, sistem de icon-uri, etc.
Plugin-urilor JavaScript incluse: tranziții, modal, tab-uri interactive, popover, scrollspy, etc.
Consistența componentelor
Dezvoltat folosind CSS3, HTML5 și cele mai noi librării jQuery
Design-ul este simplist și curat: cu cât este mai ușor să folosești interfața unui site web, cu atât experiența utilizator este mai bună.
Totuși, acest framework a fost modificat pentru framework-ul BForms pentru a aduce îmbunătățiri visuale componentelor dezvoltate, mai ales partea de CSS, și datorită anumitor bug-uri existente în versiunea curentă de Bootstrap.
3. DESCRIEREA APLICAȚIEI
Numele aplicație este JobProfiler.IT. Aplicația fost concepută ca o platformă de recrutare pe domeniul IT, unde companiile pot să posteze joburi, cu posibilitatea de a adăuga un formular de interviu care poate fi personalizat după nevoile angajatorului. Beneficiile aduse pentru cei care sunt în căutare unui job în domeniul IT: simplitatea modului de a se înregistra și a de completa informații, cu posibilitatea de a adăuga informații de pe profilul lor de Linkedin și a cv-ului lor. Majoritatea site-urile de recrutării sunt greu de folosit, necesită mult timp de a completa informați, fiecare cu standardele sale. Un alt aspect observat la site-urile de angajării din România, este lipsa upload-ului de CV personal, unde pentru un om din domeniul IT, este foarte necesar și deoarece standardele nu sunt specializate pe un profil al unui dezvoltator, multe informații fiind excluse din start.
Ideea din spate a fost să ușurez modul de folosire al unui site de recrutare, să îl specializez pe domeniul IT și să ofer un produs care se folosește de mediile de socializare virtuale pentru a colecta informați și pentru a oferi notificări prin intermediul acestora.
3.1. Pagina pagina
Fig. 3-1
Pagina de start a fost gândită pe module, cuprinzând patru categorii de informații:
Toate joburile disponibile cu posibilitatea de căutare avansată, în ordine descrescătoare a publicării lor
Primele cinci joburi, care au numărul cel mai mare de aplicanții
Primele cinci companii, care au numărul cel mai mare de aplicanții
Cele mai noi cinci companii care s-au înregistrat
Căutarea avansată se poate face după patru câmpuri diferite:
Numele jobului, câmp de tip text
Compania, lista cu selecție multiplă a tuturor companiilor inregistrate
Locația, câmp de tip text
Tehnologi de dezvoltare, lista cu selecție multiplă a tuturor limbajelor de programare, framework-urilor și cuvintelor cheie existe în bază de date
Fig. 3-2
Implicit, formularul de căutare este ascuns. Pe acest formular de căutare se pot executa trei acțiuni : căutare, resetare formular și ascundere/afișarea formularului. Căutarea se realizează prin AJAX, fără a se mai reîncărca pagină la acțiunea de salvare a formularului.
Sub formularul de căutare, vom regăsi cele mai recente cinci joburi disponibile, cu posibilitatea de a naviga prin toate joburile prin intermediul paginatorului. Rezultatele vor fi aduse împărțite în câte cinci joburi.
Fig. 3-3
Rezultate după acțiunea de paginare:
Fig. 3-4
Deasemenea, paginarea ține cont și de rezultate din formularul de căutare, dacă există informați completate.
Acest comportament a fost dezvoltat folosind componenta de grid a framework-ului BForms, cu mici modificări pentru a genera un șablon diferit de rânduri.
Fiecare element al grid-ului reprezintă un job, care conține următoarele informații:
Nume companie
Imagine companie
Nume job
Numărul de posturi disponibile
Locația jobului
Tehnologiile care sunt necesare
Link către pagina de aplicare a unui job
Link către pagina de detalii a unui job
Fig. 3-5
Pentru o companie, s-au afisat următoarele informatii
Nume companie
Numărul de joburi disponibile
Locatia principală a companiei
Tehnologiile principale ale companiei
Link către pagina de detalii a unei compani
Fig. 3-6
3.2. Pagina de detalii a unui job
Pagina de detalii a unui job este publică, nu necesită autentificare și au fost afișate următoarele informați:
Nume jobului
Numele companiei
Descrierea jobului
Numărul de posturi disponibile
Tehnologiile principale, necesare pentru a aplica la acest job
Locația jobului
Data publicării
Termenul limită pentru aplicare
Link către pagina de aplicare a jobului
Fig. 3-7
3.3. Pagina de detalii a unei companii
Pentru de detalii a unui job este publica, nu necesită autentificare si au fost afișate următoarele informații:
Nume companiei
Descrierea companiei
Tehnologiile principale
Numărul de angajati
Pagina de prezentare
Profil public Facebook
Profil public Linkedin
Joburile disponibile
Fig. 3-8
3.4. Tipuri de utilizatori și permisiunile acestora
Pentru această aplicație, există patru tipuri de utilizatori: utilizator neînregistrat, utilizator înregistrat, companie și administratorul aplicației.
Utilizatorul neînregistrat are voie sa vizualizeze paginile enumerate în subcapitolele anterioare, pagină de înregistrare și pagină de autentificare.
Utilizatorul înregistrat are permisiuniile unui utilizator neînregistrat, dar și posibilitatea de a aplica pentru un job, acces la pagină profilului unde poate vizualiza și edita informațiile sale
Utilizatorul înregistrat poate înregistra o companie, pe care să o gestioneze, astfel utilizatorul înregistrat devine defapt o companie, și pierde posibilitatea de a mai aplica la un job și anumite câmpuri din pagina de profil a utilizatorului. Acest tip de utilizator are acces la următoarele pagini aferente unei companii:
Pagina de gestionare a unei companii
Pagina de gestionare a joburilor
Pagina de gestionare a interviului asociat unui job
Pagina de gestionare a aplicanților
Administratorul aplicatiei este persoana care are acces la baza de date, cu acces total la toate datele aplicației, cu posibilitatea de a modifica informații dacă este necesar. Parola asociată unui utilizator este salvată criptată, deci nu are acces la acest tip de informație.
3.5. Securitatea
Sistemul de securitatea a fost realizat prin intermediul atributului [AllowAnonymous] , care specifică ce pagini pot fi accesate de un utilizator neînregistrat. Pentru metodele care nu conțin acest atribut, se verifică datele din sesiune și din cookies, pentru a permite sau nu accesul. În cazul în care accesul nu este permis, utilizatorul este redirectat către pagină de autentificare.
Deoarece datele din sesiune se stochează pe un timp limitat, am folosit un sistem de salvarea a username-ului în cookie și de fiecare data când se încearcă accesarea unei metode care necesită securitate, se verifică dacă cookie-ul mai există și dacă da, preiau username-ul, citesc din baza de date datele necesare și le stochez din nou în sesiunea.
Pentru a verifica dacă un utilizator este defapt o entitate de tip companie, s-au decorat controller-ele ce țin de partea de management a unei companii, prin intermediul atributul [AuthorizeRole(Role = SecurityRoles.CompanyOwner)].
O parte din codul care se ocupă cu gestionare sesiunii și a securități – Fig. 3-9
3.6. Crearea unui cont
Pentru a accesa pagina de înregistrare, trebuie să accesăm link-ul “Register” din lista “User Account” situată în partea de dreapta-sus a paginii. Există două validări implementate pe partea de server-side, și anume: username unic și email unic. Pentru datele introduse în imaginea de mai jos, au fost generate erori pentru unicitatea username-ului și a emailului:
Fig. 3-10
Dacă înregistrarea a fost efectuată cu success, utilizatorul este redirecționat spre pagină profilului lui.
3.7. Profilul unui utizator înregistrat
Fig. 3-11
Profilul unui utilizator este împărțit în cinci module, dezvoltate folosind componenta panel a framework-ului BForms:
Profile: cele mai importante informați ale unui utilizator, inclusiv posibilitatea de a adauga un CV, o poza de profil sau înregistrarea aplicatiei de Facebook pentru a primi notificări
Advance: obiective, salariu dorit, abilități, sex, starea civilă și data nașterii
Fig. 3-12
Account: informațiile contului utilizatorului, inclusiv schimbarea parolei
Fig. 3-13
Contact: datele de contact și link-urile către cele mai importante rețele de socializere:
Fig. 3-14
Linkedin: cel mai important modul al profilului, ce constă în datele tehnice ale profilului utilizatorul de pe rețeaua Linkedin
Deși există un format standard european de CV, pentru această aplicație a fost adăugat un model de CV deja completat, modificat pentru mediul IT. Componenta care se ocupă cu upload-ul de CV, oferă și următoarele caracteristici: reactualizarea CV-ului, download-ul de CV și ștergerea acestuia.
Fig. 3-15
Pentru conectarea cu mediile sociale, s-au folosit utilitarele OWIN si KATANA, specializate pe conectării Oauth2 și a noului mod de gestionare a identității unui utilizator Microsoft.AspNet.Identity, a ASP.NET MVC-ului 5. Momentan, aplicația suportă conectarea cu Facebook si Linkedin. Pentru GooglePlus, GitHub, Twitter s-a ales forma standard prin introducerea link-ului de profil.
Pentru a permite accesul informațiilor de pe Facebook si Linkedin, s-au implementat aplicații pentru Facebook si Linkedin. Doar aplicatia de Facebook conține o
implementare vizuală pentru a putea vedea direct notificările.
O parte din codul care gestionează informațiile obținute de pe mediile externe – Fig. 3-16
3.7.1 Profilul Linkedin
Cel mai important aspect al acestei aplicați, a fost modul de conectarea cu mediile externe pentru a obține informații despre utilizator. Linkedin este cea mai folosită rețea de socializare, particularizată pe profilul tehnic al utilizatorilor. Majoritatea dezvoltatorilor folosesc Linkedin pentru a adauga proiecte, certificării, experinta profesională și cel mai important, experiență tehnologiilor și a limbajelor de programare.
Pentru a putea preia informatiilor de pe mediul de socializare Linkedin, am folosit utilitare open-source, pe care l-am modificat pentru a putea preia profilul complet al unui utilizator. Codul sursa este disponibil si pe GitHub : https://github.com/smariussorin/Owin-Linkedin-Full-Profile-Implementation .
Datele sunt primite prin intermediul API-ului Linkedin, dacă utilizatorul acceptă să folosească aplicația Linkedin a acestei aplicați. Informațile sunt generate prin trimitea următorului url la api-ul Linkedin:
https://api.linkedin.com/v1/people/~:(id,last-modified-timestamp,summary,interests,positions,skills,certifications,educations,courses,projects)
API-ul Linkedin returnează un obiect de tip JSON(Jobject) și acesta este gestionat astfel:
Fig. 3-17
Datele sunt salvate în baza de date pentru a oferi persistența informatiilor.
Pasi de adăugare a informaților din profilul de Linkedin al utilizatorului:
Se apasă pe butonul de “Sign in with Linkedin to add your information”
Fig. 3-18
Automat, pagina se va redirecta spre pagina aplicației din cadrul platformei Linkedin, pentru a permite aplicației să acceseze datele utilizatorul
Fig. 3-19
După ce datele au fost gestionate si salvate, se afișează pagina profilului cu datele completate
Fig. 3-20
Prin intermediul butonului “Update Linkedin Profile”, se pot actualiza datele existente.
Profilul Linkedin poate conține următoarele informații:
Data si ora ultimei actualizări
Data si ora ultimei modificări, a profilul pe Linkedin
Informații sumare ale utilizatorului
Interese
Certificări obținute
Proiecte la care a contribuit
Experiență profesională
Aptitudini tehnice deținute
Cursuri profesionale sau didactice
Experiența educațională
3.8. Inregistrarea unei companii
Fig. 3-21
Acesta pagină este disponibilă doar utilizatorilor înregistrați. Un utilizator poate să înregistreze doar o companie asociată contului său. Accesarea acestei pagini se realizează prin intermediul link-ului “REGISTER COMPANY” din partea de sus-dreapta a paginii profilului utilizatorului. Dacă datele au fost introduse corect, utilizatorul este redirectat către pagina de profil a companiei.
3.9. Pagina de gestionare a profilului a unei companii
Fig. 3-22
Asemănător modului de profil al utilizatorului, există posibilitatea de adauga, edita sau șterge poza de profil a companiei. Există doar un panel de vizualizare și editare a informațiilor unei companii.
Pentru a accesa următoarele module pentru gestionare joburilor, se folosește link-ul din partea de sus-dreapta a paginii ‘JOBS’.
Fig. 3-23
3.10. Managementul joburilor unei companii
Fig. 3-24
Acest modul a fost realizat utilizând următoarele componente din BForms:
Toolbar (adăugare job, căutare avansata si căutare rapidă)
Grid (cu următoarele acțiunii: activare job pentru a fi vizibil, dezactivare job pentru a îl ascunde, sterge job și link către pagina de interviu, corespuzătoare unu job)
Panel (cu posibilitatea de vizualizare si editare a informațiilor unui job)
Pager
3.11. Managementul interviului unui job
Pentru a accesa această pagină, se utilizeaza link-ul “Manage Interview” din grid-ul de management a joburile.
Cum se accesează modulul de gestionare a interviului – Fig. 3-25
Imaginea de ansamblu a paginii de gestionare a unui interviu – Fig. 3-26
Un job poate avea un interviu atașat, dacă se adaugă întrebări în acest modul. O întrebare poate avea ca răspuns unul din următoarele tipuri:
ShortText – text scurt, câmp de tip text
LongText – text lung, element HTML textarea
Numeric – câmp care acceptă doar numere
RadioButtonList – listă de butoane cu selecție simplă
SingleSelect – listă cu selecție simplă
MultipleSelect – listă cu selecție multiplă
PageBreak – folosit pentru a imparti împarți în mai mulți pași
Pentru a schimbă tipul unei întrebări, se selectează tipul dorit din lista “Question type”.
Editare unei intrebari – Fig. 3-27
Vizualizarea campului de mai sus de către utilizator – Fig. 3-28
Pentru intrebările de tip RadioButtonList, SingleSelect și MultipleSelect, se folosește un câmp suplimentar. Fiecare element introdus în acest câmp va reprezenta lista de elemente asociată întrebări. Pentru a delimita mai multe elemente se folosește tastă ENTER sau SPACE. Pentru a edita unu element deja introdus, se apăsa pe elementul corespunzător și se va putea edita.
O altă caracteristică a acestui modul este posibilitatea de a ordonă întrebările. Pentru a folosi aceasta caracteristica, se apăsa butonul de “Order” din partea de sus a grid-ului.
Fig. 3-29
Astfel, toate detaliile se restrâng, se modifică aspectul vizual pentru a sugera posibilitatea de ordonare. Pentru a ordona, se selectează rândul dorit și se face prin acțiunea de drag & drop. Pentru salvare se apăsa pe butonul de “Save”, iar pentru a anula ordonarea pe butonul de “Cancel”.
Fig. 3-30
Detalii unei intrebari – Fig. 3-31
3.12. Managementul cererilor de job
Pentru a accesa acest modul, se utilizează dropdown-ului “JOBS”, din partea de sus-dreapta a paginii prin selectarea link-ului “Manage Jobs Applications”. Este realizat asemănător modului de gestionare a joburilor. Acest modul oferă informații despre utilizatorii care au aplicat pentru un job și permite gestionarea cererilor aplicatilor.
Toolbar-ul conține doar metodele de căutare avansată și căutare rapidă.
Un cerere poate avea trei stări: în asteptare (Pending), acceptată (Accepted) sau respinsă (Rejected).
Managerul companiei poate să accepte sau să respingă un utilizator. În cazul în care aplicantul este acceptat, acest utilizator este notificat prin email.
Exemplu de notificare trimisă prin email – Fig. 3-32
Modul de gestionare a aplicantilor – Fig. 3-33
Pentru a gestiona starea cererilor “Pending”, se utilizează butonul “Actions”, de tip dropdown cu următoarele opțiunii: “Accept candidate” și “Reject candidate”.
Fig. 3-34
Observăm în imaginea anterioară în detaliile unui rând, răspunsurile oferite la interviul atașat acelui job, o caracteristică importantă a acestei aplicați.
3.13. Modulul de aplicare pentru un job
Acest modul este dedicat utilizatorilor înregistrați, care nu au o companie asociată și nu au mai aplicat deja la acel post.
Un job poate cuprinde și un interviu, în funcție de preferințele companiei. Acest modul conține informați despre jobul pentru care se aplică, similar cu modulul de vizualizare a detaliilor unu job.
Un exemplu de aplicare pentru un job care nu necesită un interviu.
Fig. 3-35
Pentru a aplica pentru un job care conține și un interviu, utilizatorul trebuie să complete toate câmpurile obligatori. Un interviu poate fi împărțit pe pași, în funcție de dorința companiei.
Exemplu de interviu – Fig. 3-36
Pentru componenta de interviu a fost implementat un sistem folosind tipurile întrebărilor. Dacă au fost definite întrebării de tip “Page break”, utilizatorul v-a putea naviga înainte sau înapoi, prin intermediul butoanelor “Continue”, respectiv “Back”.
Eroare de validare a formularului de interviu – Fig. 3-37
După ce un utilizator a aplicat pentru un job, profilul său devine public și compania primește un mail cu informații despre aplicant și jobul la care a aplicat.
Exemplu de mail primit de o companie – Fig. 3-38
3.14. Profilul public al unui utilizator
Prin aplicarea la un job, un utilizator permite unei companii accesul la profilul său public. Am restricționat accesul profilul public al utilizatorul deoarece am observat că în diverse aplicați de recrutare, există persoană care se folosesc de datele utilizatorilor pentru a le oferi posturi pe diferite domenii sau a recrutanților, angajați specific pe mediul IT, uneori fiind chiar un lucru deranjant pentru unii utilizatori.
Profilul public al unui utilizator – Fig. 3-39
În imaginea anterioară este ilustrat profilul public unui utilizator. Este asemănător modului de gestionare a profilului unui utilizat, fiind eliminate elementele referitoare la contul acestuia și a anumitor butoane. Nu se pot efectua modificări asupra utilizatorului.
3.15. Aplicația pentru Facebook
O caracteristică importantă a acestei aplicați este comunicarea cu mediile externe. Momentan, există doar un tip de notificare pe Facebook și anume: utilizatorul care a înregistrat aplicația de Facebook și a oferit permisiunii, primește notificări în cadrul aplicației Facebook despre companiile care i-au vizualizat profilul.
Notificare in cadrul aplicatiei Facebook – Fig. 3-40
Profilul unei compani în cadrul aplicației Facebook – Fig. 3-41
Pentru a beneficia de această caracteristică a aplicației, utilizator trebuie să se înregistreze cu contul său de Facebook prin apăsarea butonului “Sign În with Facebook to get notifications” din pagina sa de profil.
Fig. 3-42
3.16. Planuri pentru viitor
Pe viitor, doresc să ofer această aplicație și ca aplicație nativă pentru Windows Store. Din punct de vedere al unei companii, aș dori să adaug module în funcție de necesitățile acestora, exemplu: statistica pentru durată completării unui interviu.
Dacă acesta aplicație ajunge să fie folosită de utilizatorii, am să implementez un modul de administrator în cadrul aplicației pentru a verifică informațiile postate în cadrul aplicației și pentru a putea oferi support pentru diferite necesetati ale utilizatorului.
Pentru a înțelege nevoile companilor și pentru a le dezvola, am să intru în contact cu compani pentru a le prezenta aplicația și pentru a le solicita diverse sfaturi.
O astfel de platformă poate deveni destul de utilizată prin intermediul caracteristicilor oferite și prin promovarea online.
Cea mai importantă caracteristică care vreau să o ofer acestei aplicați este generarea unui profil social pe baza rețelelor sociale. Mai exact, să implementez un algoritm de parsare și analizare a tuturor datelor unui utilizator și să ofer acest profil companilor. Această aplicație a fost dezvoltată pe bază acestei idei, dar prin analiză amănunțită a diferitelor informații, nu am putut concluziona un rezultat perfect datorită modului oamenilor cum folosesc rețele sociale și a informatilor false care le oferă.
A.ANEXE
A.1. Diagrama entitate-relație a bazei de date
A.2. Structura bazei de date
A.3. Structura aplicației
BIBLIOGRAFIE
[1] O'Reilly Media -C# 5.0 in a Nutshell, 5th Edition, 2012
[2] Daniel Solis -Illustrated C# 2012, 4th Edition, 2012
[3] Andrew Troelsen – Pro C# 5.0 and the .NET 4.5 Framework, 2012
[4] Articole din documentația jQuery – http://jquery.com/
[5] Cesar Otero – Professional jQuery, 2012
[6] Bipin Joshi – Beginning jQuery 2 for ASP.NET Developers, 2013
[7] Jon Galloway, Phil Haack, Brad Wilson, K. Scott Allen,
Scott Hanselman – Professional ASP.NET MVC 4, 2012
[8] Adam Freeman – Pro ASP.NET MVC 5, 5th Edition, 2013
[9] Jose Guay Paz – Beginning ASP.NET MVC 4,2013
[10] Adam Freeman – Pro ASP.NET MVC 4, 4th Edition,2012
[11] Articole din documentația ASP.NET MVC 5 – http://www.asp.net/mvc/tutorials/mvc-5
[12] Dean Alan Hume – Fast ASP.NET Websites, 2013
[13] William Penberthy – Developing ASP.NET MVC 4 Web Applications,2013
[14] Jess Chadwick, Todd Snyder, Hrusikesh Panda -Programming ASP.NET MVC 4,2012
[15] Articole din documentația BForms – http://bforms.stefanprodan.eu/
[16] Articole din documentația BForms – http://bforms.stefanprodan.eu/
[17] Articole din documentația RequireJs – http://requirejs.org/
[18] Articole din documentația Bootstrap – http: //getbootstrap.com/
BIBLIOGRAFIE
[1] O'Reilly Media -C# 5.0 in a Nutshell, 5th Edition, 2012
[2] Daniel Solis -Illustrated C# 2012, 4th Edition, 2012
[3] Andrew Troelsen – Pro C# 5.0 and the .NET 4.5 Framework, 2012
[4] Articole din documentația jQuery – http://jquery.com/
[5] Cesar Otero – Professional jQuery, 2012
[6] Bipin Joshi – Beginning jQuery 2 for ASP.NET Developers, 2013
[7] Jon Galloway, Phil Haack, Brad Wilson, K. Scott Allen,
Scott Hanselman – Professional ASP.NET MVC 4, 2012
[8] Adam Freeman – Pro ASP.NET MVC 5, 5th Edition, 2013
[9] Jose Guay Paz – Beginning ASP.NET MVC 4,2013
[10] Adam Freeman – Pro ASP.NET MVC 4, 4th Edition,2012
[11] Articole din documentația ASP.NET MVC 5 – http://www.asp.net/mvc/tutorials/mvc-5
[12] Dean Alan Hume – Fast ASP.NET Websites, 2013
[13] William Penberthy – Developing ASP.NET MVC 4 Web Applications,2013
[14] Jess Chadwick, Todd Snyder, Hrusikesh Panda -Programming ASP.NET MVC 4,2012
[15] Articole din documentația BForms – http://bforms.stefanprodan.eu/
[16] Articole din documentația BForms – http://bforms.stefanprodan.eu/
[17] Articole din documentația RequireJs – http://requirejs.org/
[18] Articole din documentația Bootstrap – http: //getbootstrap.com/
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Aplicație Web Profesională de Recrutare It (ID: 149546)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
