[]

  1. Asp.Net Web Forms VS Asp.Net MVC VS Asp.Net CORE
  2. Architettura del pattern MVC
  3. CORS
  4. Esempio Struttura File System
  5. Form Validation
  6. Model Binders
  7. DTO (Data Transfer Object)
  8. Session Management
  9. Web API MVC 5
  10. Web API Core
  11. Open API – Swagger
  12. Consuming API Client-Side
  13. Minimal API (.NET 6) vs Controller API
  14. Lavorare con le aree
  15. Autenticazione e Autorizzazione
  16. Ottimizzazione delle performance
  17. Deployment
  18. Filters
  19. Exception Handling
  20. MVC 5 VS MVC Core
  21. Environment e Configuration
  22. Databinding Framework
  23. .NET Watch
  24. Development Tips
  25. Risorse

 

Asp.Net Web Forms VS Asp.Net MVC VS Asp.Net CORE

  • Asp.Net Web Forms
    • 2002
    • Performance issues due to server events and view-state.
    • Windows-only
    • Not cloud-friendly
    • Not open-source
    • Event-driven development model.
  • Asp.Net Mvc
    • 2009
    • Performance issues due to some dependencies with asp.net (.net framework)
    • Windows-only
    • Slightly cloud-friendly
    • Open source (non il .NET Framework)
    • Model-view-controller (MVC) pattern
  • Asp.Net Core
    • 2016
    • Faster performance
    • Cross-platform
    • Cloud-friendly
    • Open-source
    • Model-view-controller (MVC) pattern
    • 4 Moduli principali
      • Asp.Net Core Mvc (per applicazioni web da medie a complesse)
      • Asp.Net Core Web API (per creare servizi RESTful adatti ad ogni tipo di piattaforma client)
      • Asp.Net Core Razor Page (per applicazioni web semplici)
      • Asp.Net Core Blazor (per applicazioni web usando C# per lato server e client)
        • Blazor server (applicazione ospitata lato-server)
        • Webassembly (applicatione ospitata lato-client)

 

Architettura del pattern MVC

  • Il software è suddiviso in componenti logici con competenze specifiche (Architettura multi-tier).
  • MVC Framework più diffusi (Ruby On Rails, Express, ASP.NET MVC). 

  • MVC Framework segue una serie di ‘naming conventions’ =>
    • Controller  (Binding) =>
      • Cartella “Controllers” che raggruppa tutti i “controllers”.
      • Il nome di un “controller” termina con la parola chiave Controller  (es: HomeController).
    • Action methods
      • Un “controller” é  una classe che raggruppa un set di “action methods”.
      • Ogni “action method” definisce un endpoint 
    • Model (Business Logic) =>
      • Cartella “Models” che raggruppa tutte le classi del nostro business domain.
      • Di solito le classi del “model” sono singolari mentre i nomi tabelle sono al plurale.
    • ViewModel =>
      • Sono classi che rappresentano i dati di cui una vista a bisogno nel caso che questi arrivino di più entità del “model”.
      • E’ un wrapper tra 2 o più entità del “model”.
      • E’ utile per aggiungere della logica che non va messa direttamente nelle View:
        • Businness logic
        • Presentation logic
        • Trasformation logic
      • Generalmente finiscono con la parola chiava ViewModel
      • Dovrebbe contenere i nomi dei modelli che contiene  (es: la classe che raggruppa  “PersonalDetail” and “File” si chiamerà PersonalDetailFileViewModel).

                 

    • View (Looks and positioning) =>
      • Cartella “Views” che raggruppa tutte le “view”.
      • All’interno di “Views” dovrebbe esserci una sottocartella per ogni controller contenente tutte le “view” relative a tale “controller”.
      • La funzione View(“nome-vista“) all’interno dell’action Index del controller Home ritornerà il file =>  \Home\Index\nome-vista.cshtml
  • Kestrel webserver =>

    • Cosa é =>
      • Un webserver free.
      • Open-source.
      • Cross-platform.
      • Event-driven.
      • I/O-based.
      • Asynchronous server.
      • Leggero e veloce.
      • Facile da configurare.
      • Fornito come default webserver per applicazioni .NET Core (è parte del default setup).
      • Supporta tutte le versioni di .NET Core.
      • E’ un listening-server con un interfaccia a comandi.
      • Supporta HTTP & HTTPS.

    • Perché Kestrel =>
      • Microsoft non poteva usare IIS per gestire le applicazione MVC Core in quanto voleva garantirne la multi-piattaformità.
      • Non è possibile eseguire un’applicazione MVC Core direttamente in IIS o Apache.
      • E’ un in-process server, eseguito nello stesso processo che esegue l’applicazione MVC Core.
      • Potrebbe agire come server di produzione per ospitare applicazioni .Net Core.
        • In realtà,  Kestrel è specifico dell’architettura MVC Core e non sostituisce IIS.
        • Infatti nella maggior parte dei casi viene affiancato da un Internet server vero e proprio, non offrendo Kestrel funzionalità avanzate come load balancing, caching, authentication..
    • Reverse-Proxy =>
      • In un ambiente di produzione dove è installata un ‘applicazione MVC Core si userenno normalmente entrambi i server (Kestrel + IIS) sfruttando l’architettura Reverse Proxy.
      • La richiesta verso un’applicazione MVC Core è gestita in primis da IIS che poi coinvolge Kestrel. Per la risposta il percorso sarà l’opposto.
      • Kestrel genera l’HttpContext e lo invia all’applicazione .Net Core.

    • launchSettings.json
      • Serve per conservare configurazioni che descrivono come lanciare un’applicazione .NET Core in Visual Studio.
      • E’ usato solo durante lo sviluppo di un’applicazione in Visual Studio.
      • Questo file non fa parte della cartella ‘publish’.
  • MVC vs ASP.NET Webforms =>

    • ASP.NET Webforms =>
      • View-based architecture
      • Behind code is not reusable
      • Fixed Response Types
      • Hard to test
    • ASP.NET MVC =>
      • Action-based architecture
      • Flexible combination of view and model 
      • No code behind (è possibile riusare la stessa logica in più viste)
      • No page life cycle
      • No view state
      • No server controls
      • Easy to test => Il controller sono delle classi, è facile allora poterle testare
  • Model =>
    • Cosa fa =>
      • Collezione di entità POCOs (Plain Old CRL Objects).
      • Responsabile della logica di business dell’applicazione.
      • Definisce la struttura dei dati dell’applicazione e fornisce i metodi per accedervici
      • E’ indipendente dall’interfaccia grafica.
      • Data Annotation =>
        • //Cambia la visualizzazione nel form dove si è utilizzato
          //Html.LabelFor(m => m.MermbershipTypeId) per generare la label
          [Display(Name = "Mermbership Type")]
          public int MermbershipTypeId { get; set; }
  • View =>
    • Cosa fa =>
      • Responsabile della logica di presentazione (HTML).
      • Visualizza i dati contenuti del Model.
      • Si occupa dell’interazione con utenti.
    • Strongly tiped view =>
      • Normalmente nella prima linea di una View si specifica la classe (fra quelle presenti nel Model) usata nella vista stessa.
        • //customer.cshtml
          //parola chiave @Model + tipo dell'oggetto

          //using normal Model entity
          @model Namespace.Models.MyEntity
          @Model List<Namespace.Models.Customer>

          //using ViewModel object
          @model Namespace.ViewModels.MyViewModel
      • Così all’interno della view sarà attivo l’intellisense durante la scrittura del codice c# .
      • _Layout.cshtml =>
        • E’ possibile che anche _Layout.cshtml sia associata ad un Model
        • Bisogna fare attenzione al ciclo di una chiamata in MVC:
          • La richiesta viene intercettata da un controller che chiama un’azione.
          • L’azione recupera un modello e lo passa alla vista.
          • Ogni azione passerà un modello diverso alla vista e di conseguenza alla pagina _Layout.cshtml.
          • Perciò la classe model associata a tale pagina deve essere il padre di tutte le altre.
          • Così facendo ogni azione passerà alla _Layout.csthtml un figlio differente che può agire come padre (upcasting)
          • Tale classe padre, @model della pagine  _Layout.cshtml contiene le proprietà da visualizzare in tale pagina.
        • In tal caso è possibile creare una classe ViewModel (LayoutVM) e far ereditare tutti le classi ViewModel usate nelle pagine figlie (Page1VM:LayoutVM, Page2VM:LayoutVM)  in modo che tutte possano essere caricate senza dare errore (tutti i fligli posso agire come padre). Nella classe LayoytVM è possibile aggiungere eventuali proprietà visualizzate solo nella pagina _Layout.cshtml
    • Partial View =>
      • Sono come dei widget che possono essere riusati in più View.
      • Normalmente sono messe nella cartella Shared e il loro nome inizia con _
        •  \View\Shared\_MyPartialView.cshtml
      • Sono visualizzate nelle view tramite il comando Html.Partial
        • @Html.Partial("_MyPartialView");
          //Se non si esplicita un secondo parametro allora la PartialView
          //avrà lo stesso @Model della View che la ospita.
          //E' possibile sovrascrivere questo comportamento indicando esplicitamente
          //quale Model assegnare alla nostra PartialView
          @Html.Partial("_MyPartialView", Model.Author);
    • Model Binding
      • Se una View contiene un form, una volta fatto il submit, invierà all’azione che lo gestisce un oggetto identico a quello definito come modello nella View stessa o una dei suoi attributi indifferentemente.
    • Tips =>
      • Click dx ovunque nel file <view_name>.csthml per andare al controller corrispondente.
      • Per aggiungere un commento 
        • @* This is a comment *@
    • Helper methods =>
      • //ActionLink
        @Html.ActionLink("displayed_name", //Link test
        "action_name", //Action Method Name
        "controller_name", //Controller Name
        "new { Id = movie.Id }, //Route value
        null); //<-- htmlArguments
        //TextBoxFor
        @Html.TextBoxFor(m => m.Customer.BirthDate, //
        ["{0:d MMM yyyy}",] // optional text display format
        [new { @class = "form-control" }]) //optional css cls
  • Controller =>
    • Attivazione dei controller e del routing =>
      • Classe controller
        • E’ necessario almeno una delle seguenti due azioni (o entrambe)
          • creare una classe il cui nome deve avere la parola chiave ‘controller’ alla fine (es: HomeController).
          • decorare la classe con l’attribute [Controller]
      • Registrare la classe nel DI container
        • E’ necessario aggiungere tale classe (tramite il WebApplicatonBuilder)  al DI container in modo che possa essere iniettata nei vari componenti dell’applicazione.
        • Registrare una classe nel DI container permette al ‘.NET Core runtime’ di trattare la classe come un servizio e di poterla istanziare a runtime solo quando l’HTTP Request URL comunica con tale classe.
        • //program.cs
          var builder = WebApplication.CreateBuilder();

          Metodo 1 - manuale
          //Per ognuna delle classi controller presenti nel progetto
          //registrarla manualmente come servizio aggiungendola al DI container
          builder.Services.AddTransient<HomeController>();

          Metodo 2 - automatico
          //tramite una delle seguenti 2 metodi é possibile
          //aggiungere automaticamente tutti i controlli presenti nel progetto come servizi al IServiceCollection (per gestire la DI)
          //cosi' che possano essere istanziati e acceduti solo quando lo specifico endpoint é richiesto.
          builder.AddControllers();
          [oppure builder.AddControllersWithViews();]
      • Attivare il routing verso gli ‘action methods’ contenuti nel controller.
        • //program.cs
          var builder = WebApplication.CreateBuilder();
          builder.AddControllers();

          var app = builder.Build();
          app.MapControllers();

          [ oppure
          var app = builder.Build();
          app.UseRouting();
          app.UseEndpoints(endpoints =>
          {
          //Tale metodo crea in automatico il mapping di tutti gli 'action methods' presenti nel progetto
          endpoints.MapControllers();
          }]
      • Definire il route template per ogni action methods’ contenuto nel controller.
    • Cosa fa =>
      • Raggruppa un insieme di endpoints detti ‘action methods’ e li esegue.
    • Action method =>
      • Ogni metodo publico all’interno di un controller è detto <Action>.
      • L’azione Index => E’ l'<action> di default. Chiamata quando l’URL Request punta al <controller> senza indicare nessun <action> .
      • Normalmente un metodo chiamato <nome_metodo> ha un’alter-ego con lo stesso nome tra le View. E’ comunque possibile richiamare una View diversa da quelle di default:
        • public IActionResult Edit(int id)
          {
          //l'azione fa il redirect verso edit.cshtml 
          return View();
          //l'azione fa il redirect verso la View specificata <other_view>.cshtml
          return View("other_view");
          }
      • Overload
        • E’ possibile implementare l’overload di un’action usando l’attributo [ActionName(“ActionNewName”)]
        • Cambiando cioè il nome al metodo overload al fine di dare un URL univico ad ogni action
        • public class CustomerController : Controller
          {
          public ActionResult GetCustomer()
          {
          //metodo base
          }

          [ActionName("GetGustomerWithID")]
          public ActionResult GetCustomer(Int Id)
          {
          //primo overload
          }
          }
      • [NonAction]
        • E’ possibile indicare che un metodo del nostro controller non é un ‘action method’ ma un semplice metodo non esposto all’esterno
        • [NonAction]
          public virtual void JsonResult(object? data, object? serializerSettings)
          {
          //....
          }
    • ActionParameter (E’ l’input di un’azione) 
      • Possiamo passare dei parametri ad un’ <Action> => 
        • Tramite URL:
          • //Passo passare i parametri 1 e Name al medoto <actionName>
            //Nel file RouteConfig.cs devo impostare l'opportuna configurazione
            //aggiungendo un'entry al metodo routes.MapRoute()
            <url_site>/controllerName>/<actionName>/1/Filippo
        • Tramite query string:
          • //Il metodo <actionName> deve avare un parametro chiamato <id>
            //e un parametro chiamato <name>
            <url_site>/<controllerName>/<actionName>?id=1&name=Filippo
        • Tramite i dati in POST di un form.
    • HTTP Request  (ciclo a 4 fasi) =>
      • Un controller (tramite i suoi “Action Method”) é responsabile della gestione delle HTTP Request (inviate dal client) tramite un ciclo di 4 passaggi che si ripete ad ogni nuova richiesta :
        1. HTTP Request 
          • Intercetta la richiesta HTTP inviate dal client e ne estrae i dati inviati.
        2. Validazione 
          • Gestisce la validazione dei dati inviati dall’utente in uno dei seguenti modi:
            • Query string.
            • Request Body.
            • Request Cookies.
            • Request Header.
        3. Invoca i metodi della business logic
          • Normalmente esposti come ‘services’
        4. HTTP response
          • Sceglie quale risposta inviare al client e la prepara (tramite gli action method).
    • HTTP Response =>
      • Un controller (tramite i suoi “Action Method”) é responsabile della gestione delle HTTP Response
      • IHttpActionResult (Web API MVC 5.0) =>
        • Wrapper di HttpResponseMessage
          • return Ok(products.ToList());  
            //return ResponseMessage(Request.CreateResponse(HttpStatusCode.OK, products.ToList()));
        • Pro1: Si contoncentra solo sul dato inviato e non sullo stato con codice più chiaro da mantenere e testare.
        • Pro2: Usa async e await di default.
        • Similarmente agli ActionResult in MVC, possiamo usare una serie di oggetti per gestire il parametro di uscita delle nostre API: 
        • //Equivale a throw new HttpResponseException(HttpStatusCode.BadRequest);
          return BadRequest();

          //Ritorna HTTP created status 200
          Ok(_mapper.Map<CustomerDto>(customer));

          //Ritorna HTTP created status 201
          Created(new Uri(Request.RequestUri + "/" + customer.Id), customerDto);
      • IActionResult (Asp.net core) 

 

        • Tutti i tipi che seguono implementano tale interfaccia che quindi puo’ essere tranquillamente usata come valore di ritorno di default.
        • E’ possibile all’interno di un ‘action method’ fare il return di ogni tipo di classe che implementa tale interfaccia.
        • ViewResult  => Per ritornare una View
          • return View();
        • PartialViewResult  => Per ritornare una PartialView
          • return PartialView();
        • ContentResult =>
          • Puo ritornare ogni tipo di dato.
          • Il tipi di dato é deciso dal campo control-type (presente nel Http Request header)
          • public class HomeController
            {
            public ContentResult Index()
            {
            return new ContentResult()
            {
            ContentType= "text/html",
            Content= "<html><body><h1>Pippo</h1></body>"
            };
            }
            }

            oppure

            public class HomeController : Controller
            {
            public ContentResult Index()
            {
            return Content("<html><body><h1>Pippo</h1></body>",
            "text/html");
            }
            }
        • RedirectResult => Per redirigire l’utente ad un particolare URL
          • //Invia al client HTTP status code 301
            //Indica al client che la risorsa richiesta é stata 'spostata' all'URL
            // indicato nella Http Response ricevuta.
            //Allora il client invia una nuova richiesta al nuovo URL.
            return Redirect(<url>);
        • RedirectToRouteResult => Per ridirigere l’utente ad un’altra <Action> 
          • //Invia al client HTTP status code 302
            //Indica al client che la risorsa richiesta é stata 'spostata' alla 'action'
            // indicata nella Http Response ricevuta.
            //Allora il client invia una nuova richiesta alla nuova 'acton'.
            return RedirectToAction(<action_name>,
            <controller_name>,
            //E' possibile passare dei parametri
            new {param1 = value1, param2=value2});
        • JsonResult => Ritornare un oggetto JSon serializzato.
          • public JsonResult Index()
            {
            var person = new Person() {
            Id=1,
            Name="Alberto"
            };
            //l'oggetto person viene convertito automaticamente nel formato JSON
            //e aggiunto al body delle Http response
            //il Content-type sarà => application/json
            return new JsonResult(person);

            oppure

            return Json(person);
            }
        • FileResult  => Per ritornare un file
          • //ritorna un file per il download

            1 - VirtualFileResult
            //Da usare se il file da downloadare é presente nella cartella wwwroot
            //(o qualunque cartella impostata per gestire i file statici nella nostra applicazione web)
            //VirtualFileResult("file relative path", "content type");
            return new VirtualFileResult("/prova.txt", "text/plain");
            oppure
            return File("/prova.txt", "text/plain");

            2 - PhysicalFileResult
            //Da usare se il file da downloadare é NON presente nella cartella wwwroot
            //Indicare il path assoluto del file
            return new PhysicalFileResult("file absolute path", "content type");
            oppure
            return PhysicalFile("/prova.txt", "text/plain");

            2 - FileContentResult
            //Usato quando bisogna ad esempio trasferire un'immagine reucperata dal DB o da una sorgente
            [Route("download-byte")]
            public async Task<FileResult> FileDownloadByte()
            {
            Byte[] bytes = await System.IO.File.ReadAllBytesAsync(file_path);
            return new FileContentResult(byte_array, "content type");
            oppure
            return File(bytes, "text/plain");
            }
        • StatusCodeResult
          • Ritorna una risposta vuota con lo stato specificato

            return new StatusCodeResult(status_code);
            oppure
            return StatusCode(200);

            //error state = 400
            return new BadRequestResult();
            oppure
            return BadRequest("<messaggio>");

            //error state = 401
            return new UnauthorizedResult();
            oppure
            return Unauthorized("<messaggio>");

            //error state = 404
            return new NotFoundResult();
            oppure
            return NotFound("<messaggio>");
        • EmptyResult => il metodo non ritorna alcun valore 
    • Tips =>
      • mpvaction4 => E’ lo snippet di Visual Studio per inizializzare un’action vuota. 
      • E’ possibile creare una nuova <new_view>.cshtml direttamente facendo click destro sul metodo View() persente nella nostra action.
  • Router =>
    • Cosa fa =>
      • Responsabile della gestione delle richiesta HTTP.
      • Reindirizza le richieste HTTP alla corretta Action del buon Controller secondo la configurazione. 
      • Mappa  degli URL friendly ai corretti Controller/Action
      • L’ordine dei mapping conta
      • Il mapping può essere centralizzato in una tabelle del DB.
      • Permette di ottimizzare il SEO (Search engine optimization)

 

CORS (Cross Origin Resource Sharing)

  • Meccaniscmo di sicurezza degli attutali browser.
  • Cross-domain issue =>
    • Le architetture multi layer dividono concettualmente i progetto in livelli con differenti responsabilità.
      • A volte questo si riflette  nella divisione fisica del nostro progetto.
    • Tale divisione fisica fa si che ci saranno chiamate cross domain all’interno del progetto.
      • Più specificatamente il layer UI dovranno accettare chiamate cross domain.
  • Stessa origine =>
    • Una stessa origine significa 2 indirizzi che condividono PROTOCOLLO + DOMINIO [+ PORTA (se presente)] 
    • //stessa origine
      https://www.stackhawk.com
      https://www.stackhawk.com:443
      https://www.stackhawk.com/why-stackhawk/

      //origine diversa
      - primo e secondo non condividono il protocollo
      - secondo e terzo non condividono il dominio
      http://www.stackhawk.com
      https://www.stackhawk.com
      https://stackhawk.com/why-stackhawk/
  • Abilitare CORS lato-server (ASP.NET Core) =>
    • Per motivi di sicurezza un’applicazione web ASP.NET Core non permette di default le richieste CROSS-ORIGIN
    • Customizzare comportamento rispetto alle chiamate crosso-dominio
      • Program.cs =>  
        • var builder = WebApplication.CreateBuilder(args); 
          var app = builder.Build();

          - STEP1
          builder.Services.AddCors(options =>
          {
          [solo in DEV
          options.AddDefaultPolicy(builder =>
          {
          builder
          .AllowAnyOrigin()
          .AllowAnyMethod()
          .AllowANyHeader()
          });]

          options.AddPolicy(name: policyName,
          builder =>
          {
          builder
          .WithOrigins("http://localhost:3000") //.AllowAnyOrigin()
          .WithMethods("GET")
          .AllowAnyHeader()
          });
          });


          - STEP2
          var policyName = "_myAllowSpecificOrigins";
          app.UseCors(policyName);
      • Atribute Filter =>
        • //nei progetti che ospitano i livelli esterni chiamati dal progetto web 
          //che gestisce la UI (la vera e propria web application)
          using System.Web.Http.Filters
          public class AllowCrossSite : ActionFilterAttribute
          {
          public override void OnActionExecuted(HttpActionExecutedContext actionExcetutedContext)
          {
          if(actionExcetutedContext != null)
          actionExcetutedContext.Response.Headers.Add("Access-Control-Allow-Origin");

          base.OnActionExecuted(actionExcetutedContext);
          }
          }

          [AllowCrossSite]
          public class CustomerApi : ApiController
          {
          ...
          }

 

Esempio Struttura File System

    • \App_Start //Cartella con classi eseguite all'aperutra dell'applicazione
      - BundleConfig.cs
      - FilterConfig.cs
      - RouteConfing.cs
      routes.MapRoute(
      name:"Default"
      url: "{controller}/{action}/{id}",
      defaults: new {
      controller = "Home",
      action = "Index",
      id = UrlParameter.Optional
      },
      //posso applicare del constraint ai parametri in input
      new {id = @"\d{2}");

      \Content //Cartella che contiene css
      \Controllers //Per aggiungere un nuovo controller, click dx, add controller
      - AccountController //Contiene le Action per il login, deriva dalla classe Controller
      ...
      - HomeController //Contiene le Action per la Home Page, deriva dalla classe Controller
      \ViewModels
      \Views
      (Per aggiungere una nuova View, click dx, add View)
      - Account (E' la View relativa all' AccountController)
      ...
      - Home (E' la View relativa al HomeController)
      - \Shared (Contiene eventuali Views usate da più Controller)
      - _Layout.cshtml (è la MasterPage dell'applicazione)
      - _viewStart.cshtml
      - web.config
      \Models
      Web.config \\Include configurazione dell'applicazione
      - conncectionString
      - appSetting
      Packages.config //Configura il Package Manger, responsabile delle dipendenze esterne
      Global.axa.cs //Gestisce diversi eventi nel ciclo di vita dell'applicazione
      protected void Application_Start()
      {
      AreaRegistration.RegisterAllAreas();
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      //E' possibile registrare una serie di Bundle per ottimizzare
      //il download delle risorse linkate ragruppandole in gruppi di download
      //chiamati appunto Bundle
      BundleConfig.RegisterBundles(BundleTable.Bundles);
      }
      Startup.cs //In .NET Core rimpiazza il Global.axa

 

Form Validation

  • Come funziona (quando l’utente invia i dati al server in “post”) =>
    • Il modello dati viene riempito con i dati indicati dall’utente.
    • Scatta una validazione automatica.
    • Il risultato di tale validazione è presente nel ModelState (nell’action/controller che sono target del form in POST usato dall’utente)

  • Attivare la validazione lato server (per far scattare la validazione del model riempito dall’utente è necessario eseguire i seguenti 3 passi) =>
    • Data Annotation:
      • Aggiungere le opportune data annotations alle proprietà delle classi del model.
      • using System.ComponentModel.DataAnnotations;

        [Required(ErrorMessage="Please enter a value!")]
        [StringLength(255)]
        [Range(1,10)]
        //Confronto il campo con un'altro campo presente nel form
        [Compare("other_property_name")
        [Phone]
        [Email]
        [Url]
        [AllowHtml]
        [RegularExpression("^[A-Z]{3}[0-9]{4}$")]
    • ModelState.IsValid:
      • Intercettare eventuali errori di validazione nel nostro form.
    • Validation Method =>
      • Aggiungere gli opportuni metodi di validazione nel nostro form.
      • Tutte le proprietà delle entità del nostro model che non sono nullable (esplicitamente o implicitamente) saranno richieste anche senza esplicitamente aggiungere la data annotation [Required].
      • Piazzare il PlaceHolder dove verrà visualizzato il messaggio di validazione per tutte le proprietà nel nostro form che si vogliono validare.
        • @Html.ValidationMessageFor(m => m.Customer.Name)
      • [Opzionale] Piazzare il PlaceHolder per visualizzare la lista degli errori di validazione
        • //visualizza la lista degli errori di validazione
          @Html.ValidationSummary();

          //Non visualizzo ognuno dei messaggi ma solo un messaggio unico
          @Html.ValidationSummary(true, "Verificare i seguenti errori");
  • Customizzare gli errori =>
    • Stile => E’ possibile aggiungere classi al css (\Content\Site.css) per customizzare lo stile dei messaggi 
      • //messaggio di errore in rosso
        .field-validation-error {
        color:red;
        }

        //input box sottolineato con bordi rossi
        .input-validation-error {
        border: 2px solid red;
        }
    • Aggiungere logica => E’ possibile aggiungere delle business rules legate all’inserimento dei dati. Eseguire i seguenti 3 passi:
      1. Creare una nuova classe che eredita da ValidationAttribute dove mettere le nostre business rules.
        • using System.ComponentModel.DataAnnotations;
          public class Min18Years : ValidationAttribute
          {
          protected override ValidationResul IsValid(Object value
          ValidationContext vc)
          {
          var customer = (Customer)vc.ObjectInstance
          //aggiungere la business rule desiderata
          return ValidationResult.Success;
          oppure
          return new ValidationResult("il cliente deve essere maggiorenne");
          }
          }
      2. Aggiungere l’ annotatione customizzata alla proprietà dell’entità alla quale si vuole aggiungere la nostra business rule.
        • [Min18Years]
          public DateTime BirthDate { get; set; }
      3. Aggiungere (se non esiste già) il PlaceHolder nel nostro form per la proprietà alla quale abbiamo aggiunto la nostra business rule.
    • Non possono funzionare lato-client.
  • Attivare la validazione lato client =>
    • Per evitare una chiamata al server inutile (in caso i dati non rispecchino la validazione) è meglio attivare la validazione lato client 

 

    • Tramite le data annotation MVC può generare oltre che la validation server side come visto sopra anche la validazione lato client 
    • RazorView engine durante l’esecuzione del comando @Html.TextBoxFor verifica se ci sono delle “Data Annotation” legate alla proprietà che mappa questo campo del form e crea degli attibuti nel <input> tag che jquery riconosce e usa per genera gli opportuni script lato client per la validazione dell’input.
    • Per attivare la validazione è necessario includere nel nostro form il bundle per la validazione (definito nella classe BundleConfig nella cartella App_Start)
      • //aggiungere alla pagina .cshtml dove vogliamo attivare la validazione lato client
        @section scripts
        {
        @Scripts.Render("~/bundles/jqueryval");
        }

 

Model Binders

  • //classe CustomerBinder
    //creo binder per la classe Customer
    public class CustomrBinder : IModelBinder
    {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {             HttpContextBase objContext = controllerContext.HttpContext;             string custCode = objContext.Request.Form["txtCustomerCode"];             string custName = objContext.Request.Form["txtCustomerName"];             var obj = new Customer()             {                 CustomerCode = custCode,                 CustomerName = custName             };             return obj;         }
    }

    //CustomerController
    //Recupero l'oggetto Customer dopo averne fatto il bind con la classe CustomerBinder
    public class CustomerController : Controller
    {
    [HttpPost]
    public ActionResult Submit([ModelBinder(typeof(CustomerBinder))] Customer obj)
    {
    return View("customer", obj);
    }
    }

 

DTO (Data Transfer Object)

Per impedire di esporre direttamente gli oggetti del MODEL sia nei form delle applicazioni ASP.NET MVC sia negli oggetti trasferiti alle ASP.NET WEB API è una pratica normale esporre delle semplici classi POCO dette DTO.

  • Vantaggi dell’uso di DTO al posto delle classi del MODEL =>
    • Affidabilità => Permette di ridurre la possibilità che modifiche al nostro application domain model abbia un impatto sull’usabilità delle risorse fornite dall’applicazione stessa. 
    • Sicurezza => Permette di non esporre (nei form o nei dati scambiati dalle API) il modello dell’applicazione con benefici in termini di sicurezza.
    • Semplicità => Evita eventuali problemi di serializzazione.

 

Session Management

  • Cosa è una sessione?
    • Una sessione è l’interazione che l’utente ha con il server da quando apre a quanto chiude il browser.
  • Protocollo HTTP stateless =>
    • Il protocollo HTTP è nativamente stateless.
    • Tratta ogni richiesta/risposta come una singola unità. Se l’applicazione vuole implementare il concetto di sessione
    • Restart server =>
      • In caso di in-proc server le variabili sessione e tempData non sono mantenute.
      • In caso di out-proc server le variabili sessione e tempData potrebbero essere mantenute.
  • Modalità di gestione della sessione
    1. Session variables / HttpContext =>
      • HttpContext => Contiente i dati di una HTTP Request e della relativa HTTP Response + eventuali dati di sessione.

      • Ogni utente avrà un set di variabili sessione separato. Le variabili sessione non hanno un valore unico nell’applicazione ma sono legate ad un utente e ad una sessione di tale utente in particolare.
      • La sessione è gestita tramite cookies memorizzati nel computer dell’utente (se i cookies sono attivi lato client)
        • Grazie all’utilizzo dei cookies lato client, Il protocollo HTTP diventa artificialmente statefull
        • Se i cookies sono disattivta bisogna pensare ad un meccanismo di gestione delle sessioni differente
          • hidden field
          • query string parameters
        • Session timeout => se l’utente è inattivo per un certo tempo (session timeout), le variabili di sessione scadono.
      • ASP.NET MVC 5 => attiva di default
      • ASP.NET MVC Core => disattiva di default
        • Per usare le variabili di session in ASP.NET MVC Core bisogna
          1- Attivare la gestione delle variabili sessione nel file statup.cs
          //Startup.cs
          public void ConfigureServices(IServiceCollection services)
          {
          services.AddSession(options => {
          options.Cookie.Name = ".MyApp";
          options.IdleTimeout = TimeSpan.FromSeconds(10);
          options.Cookie.HttpOnly = true;
          options.Cookie.IsEssential = true;
          });
          }


          public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
          {
          app.UseSession();
          }

          2- definire e usare le variabile session nel nostro codice HttpContext.Session
          if(HttpContext.Session.TryGetValue("counter", out var x))
          {
          i = HttpContext.Session.GetInt32("counter");
          }
          i++
          HttpContext.Session.SetInt32("counter", i);
    1. ViewData / ViewBag =>
      • Normalmente usati per visualizzare pochi dati (altrimenti è meglio utilizzare i ViewModels).
      • Passaggio dati da Action => View
      • Ad esempio, In caso di RedirectToAction all’interno di una stessa sessione i dati memorizzati in una variabile ViewData non sono permanenti.
      • Lo scope di una variabile ViewData è dal controller dove la variabile è definita alla relativa View.
      • Non permettono il passaggio di valori da un’azione all’altra all’interno di una HTTP Request.

      • ViewBag =>
        • Offre una sintassi più semplice rispetto a ViewData (syntactic sugar).
        • Al suo interno la variabile è memorizzata sfruttando ViewData.
        • Al suo interno usa un oggetto di tipo dynamic (valutato in fase di runtime).
        • ViewBag.conta = 3
      • View Data =>
        • ViewData["conta"] = 3;
    1. TempData =>

       

 

      • Può persiste dati tra 1 o più richieste successive.
      • Una variabile “tempData” ha quindi uno scope:
        • potenzialmente più ampio delle variabili di sessione (il cui scope è appunto una sessione, apertura/chiusura browser o il timeout).
        • molto più ambio di varibili ViewBag/ViewData (il cui scope è tra il controller la relativa view) 
      • Sono differenti dalle variabili di tipo sessione perché è possibile decidere su quante richieste persistere i dati, mentre per le variabili sessioni sarà il timeout o la chiusura del browser a deciderlo.
      • All’interno di una HTTP Request :
        • le variabili “tempData” persistono il loro valore usando variabili di tipo sessione internamente.
        • le variabili “tempData” permettono il passaggio di valori tra 2 action (se si fa un redirect all’interno di una HTTP Request).
      • Tra una HTTP Request e un’altra (di una stessa sessione) =>
        • Finchè la variabile tempData non viene letta, il suo valore è mantenuto.
        • Keep => Per mantenerne il suo per la HTTP Request successiva (anche dopo lettura) usare Keep per leggere tale variabile
          • TempData["td"] = "Hello";
            var x = TempData["td"];
            TempData.Keep("td");
        • Peek => leggere la variabile tempData + keep
          • Syntactic suger da usare al posto di Keep.  Infatti internamente Peek è implementato chiamando Keep. 
          • TempoData["td"] = "Hello";
            var x = TempoData.Peek["td"];

 

Web API MVC 5 (Application Programming Interface)

  • Cose é =>
    • Un controller Web Api può esporre servizi o dati facilmente usando il protocollo HTTP che possono essere quindi utilizzati da diversi client:
      • Browser
      • Mobile App
      • Javascript framework

    • API Controller vs MVC Controller:
      • Le Web Api presentano un’architettura simile ai controller MVC ma ritornano al client row data e non del markup html (come ASP.NET MVC) che sarà invece creato lato client.
  • Alternative (altre tecnologie per implementare “remote procedure”):
    • gRPC
    • WebSocket
    • Signalr (utilizzato in Blazor)
  • Architettura REST  (Representational State Transfer) =>
    • Quando una richiesta viene inviata a un’API RESTful, la risposta è una rappresentazione in un certo stato (valore) del dato richiesto
      • E’ possibile operare con molte sorgenti di dati (Video, File di Testo, Database, Immagini)
      • Ognuna di queste sorgenti verrà rappresentata in un formato JSON, XML o HTML e inviata al client sfruttando il protocollo HTTP e le sue capacità native.
      • Il fatto che si trasferisca una rappresentazione dei dati significa che abbiamo un completo disaccoppiamento tra il dato e il client che lo riceve.
    • Mantenimento della sessione
      • Per un servizio RESTFul che è STATELESS (http) è possibile mantenere la sessione tra richieste tramite Token Based Authorization.
    • Protocollo HTTP
      • Usa tale protocolo per gestire la comunicazione tra client e server.
      • Vantaggio => Semplicità e standardizzazione
    • URI (Uniform Resource Identifier) => Un’API RESTful è definita da un indirizzo Web o URI. 
    • Stateless
      • Una comunicazione RESTful è per definizione stateless.
      • Vantaggio => E’ facilmente scalabile.
    • Cache
      • Buone performance grazie al’uso del mecchanismo della cache.
  • Restful API 
    • Le Web Api permetto di implementare un servizio secondo l’architettura REST. Si parla quindi di Restful API.
  • Come implementare un servizio RESTful =>
    • Rispettare lo standard HTTP
      • Nella progettazione degli end-point meglio non implementare proprie variabili (query string o POST).
      • Piuttosto usare le informazioni fornite dal protocollo:
        • HTTP Request
          • Header =>
            • Accept, Host, User-Agent, Pragma
            • Content-type
              • Ospita il MIME (Multipurpose Internet Mail Extensions) cioé il tipo di dato presente nel Http Request Body
              • Application
                • application/javascript
                • application/pdf
                • application/json
                • application/xml
                • application/zip
                • application/x-www-form-urlencoded
              • Testo
                • text/html
                  text/plain
                  text/xml
              • Media
                • audio/mpeg
                • image/gif
                • image/jpeg
                • image/png
          • HTTP Method => Operazione (es: Get)
          • URI => EndPoint / Resource (es/ /api/product)
          • Parameters (es: productid=1)
          • Body
        • HTTP Response
        • HTTP Status Code
      • Non usare querystring negli URL per passare dati ma solo per informazioni aggiuntive o session management.
      • Indirizzi
        • Operazione + EndPoint + Parametri
        • Gli end-point puntano alle ‘resource’
      • Mappging operazioni CRUD
        • Sfruttare le capacità native di HTTP, come GET, PUT, POST e DELETE per semplificare l’URI.
        • 1 HTTP Method = 1 Operazione
        •  Convenzione
          • Get (Read)
            • retrieves a list of resources  => /api/customers
            • retrieves one resource  => /api/customers/:id
          • Post (Create)
            • submits new (non-idempotent) data to the server=> /api/customers
            • da usare per esporre  ‘controller actions‘ (procedure) che operano molte modifiche nel DB
          • Put (Update)
            • updates existing data or submits new data (in caso di idempotent o controllato dal client (id generato dal client)) => /api/customers/:id
          • Patch (Update)
            • customized or partial update (invio al server solo alcuni campi dell’entità che voglio modificare e non tutti come con PUT)
          • Delete (Remove)
            • removes data => /api/customers/:id 
  •  
  • Content negotiation =>
    • In funzione del client (Http Request header accept) e di quale formato richieste il nostro servizio Web Api può reagire differentemente e fornire i dati secondo la rappresentazione voluta
    • Mentre nei normali controllo il tipo di rappresentazione in uscita è cablato nel codice e il client non può ricevere altro che quello.

  • App_start\WebApiCongif.cs => ospita il settaggio del routing
    • using Newtonsoft.Json;
      using Newtonsoft.Json.Serialization;

      public static class WebApiConfig
      {
      public static void Register(HttpConfiguration config)
      {
      //Setta la serializzazione degli oggetti in Json a rispettare
      //la convezione CamelCase per i nomi delle proprietà
      //così da rispettare lo standard usato in Javascript
      var setting = config.Formatters.JsonFormatter.SerializerSettings;
      setting.ContractResolver = new CamelCasePropertyNamesContractResolver();
      setting.Formatting = Formatting.Indented;

      config.MapHttpAttributeRoutes();

      config.Routes.MapHttpRoute(
      name: "DefaultApi",
      routeTemplate: "api/{controller}/{id}",
      defaults: new { id = RouteParameter.Optional }
      );
      }
      }
  • WebApi vs WCF ( Windows Communication Foundation)

  • WebAPI vs SOAP
    • SOAP (Simple Object Access Protocol)
      • E’ un vero e proprio protocollo e non una serie di principi.
      • Può utilizzare HTTP, SMTP
      • Scambia XML
      • WSDL (WebService Description Language) =>
        • File XML per descrivere le proprietà del web service esposta dall’API SOAP
          • Permette di sapere come costruire il messaggio di richiesta
          • L’indirizzo dove mandare la richiesta.
      • XML (Xml Schema Definition) => L’API Soap può validare XML di richiesta e l’XML di risposta

 

Web API Core

  • SerializzazioneIgnorare la dipendenza circolare (program.cs)  
    • Qualora non sia possibile evitare la dipendenza circolare tra 2 oggetti é allora possibile evitarla: 
      • //Evito di incorrere nell'errore di serializzazione dovuta alla dipendenza circolare tra gli oggetti nel mio model
        //Ad esempio nella relazione 1 a molti tra un autore e i suoi libri
        //Nell'entità <Autore> c'é la proprietà che espone la lista dei <Libri>
        //Nell'entità <Libro> c'é la proprietà con l'<Autore>
        //Le due entità sono quindi legate circolarmente: se provo a deserializzarle entro in un loop infinito
        //Per ovviare a questo problema imposto il middleware di serializzazione per ignorare
        // le dipendenze circolare in fase di serializzazione/deserializzazione.

        //ASP.NET Core 6+
        builder.Services.AddControllers().AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);

        //ASP.NET Core 5
        builder.Services.AddControllers().AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve);

        //ASP.NET Core 3.1 or less
        //istall Microsoft.AspNetCore.Mvc.NewtonsoftJson
        services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);

    • JsonIgnore attribute
      • using System.Text.Json.Serialization;

        public class Character
        {
        public int Id {get; set;}
        public string Name {get; set;} = string.Empty;
        [JsonIgnore]
        public User User {get; set;} = default!;
        public int UserId {get; set;}
        }
  • Profili di debug
    • Builtin-in Kestrel (As a Console App)
      • Per avere un controllo migliore è preferibile fare il debug di una WebApi Core  eseguendola come un’applicazione console (e non in IIS Server) sfruttando il web server built-in in-process.
        • Infatti nel cloud spesso le applicazioni vengono eseguite in Linux quindi non è più opportuno testarle in IIS.

    • Come eliminare un profilo di debug
      • E’ possibile eliminare un profilo (es: IIS Express) cliccando su ‘<nome-progetto> Debug Properties’

  • Controller
    • Routing
      • Standard setting per trasformare una normale class in un controller API

      • //http:<my-url>/api/products/all
        [Route("api/products")]
        [ApiController]
        public class ProductController : ControllerBase
        {
        [HttpGet("all")]
        public IActionResult GetAll(){
        products = _dbcontext.Products.ToList();
        return Ok(products);
        }
        }
    • CRUD
      • Get
        • HTTP Status code => E’ importante che ogni metodo implementato nei controller API ritorni il corretto codice HTTP
        • [HttpGet]
          [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Event>))]
          public IActionResult GetAll()
          {
          return Ok(repository.GetAll());
          }

          [HttpGet("{id}", Name = nameof(GetById))]
          [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Event>))]
          [ProducesResponseType(StatusCodes.Status404NotFound)]
          public IActionResult GetById(int id)
          {
          var myobject = repository.GetById(id);

          if(myobject == null) return NotFound();

          return Ok(myobject);
          }

          //esempio chiamata asincrona
          public async Task<ActionResult<List<Character>>> GetAll()
          {
          var characters = await _dbContext.Characters
          .Include(u => u.User)
          .ToListAsync();

          return characters ;
          }
      • Add
        • [HttpPost]
          [ProducesResponseType(StatusCodes.Status201Created, Type = typeof(Event))]
          [ProducesResponseType(StatusCodes.Status400BadRequest)]
          public IActionResult Add([FromBody] Event newEvent)
          {
          if (newEvent == null)
          //400
          return BadRequest("Invalid input");

          repository.Add(newEvent);
          //_dbContext.Add(newEvent);
          //await _dbContext.SaveChangesAsync();

          //201
          //return Created("todo", typeof(Event));

          //201 e redirect al metodo GetById con il parametro in ingresso {newEvent.Id}
          return CreatedAtAction(nameof(GetById), new { newEvent.Id }, typeof(Event));
          }
        • CreatedAtAction

      • Delete
        • //htpps:myserver.com/api/events/494
          [HttpDelete]
          [Route("{eventToDelete}")]
          [ProducesResponseType(StatusCodes.Status204NoContent)]
          [ProducesResponseType(StatusCodes.Status400BadRequest)]
          [ProducesResponseType(StatusCodes.Status404NotFound)]
          public IActionResult Delete(int eventToDelete)
          {
          if (eventToDelete == 0)
          return BadRequest("Invalid ID supplied");

          try
          {
          repository.Delete(eventToDelete);
          }
          catch (ArgumentException)
          {
          return NotFound();
          }

          return NoContent(); //oppure return Ok(true);
          }

 

Open API – Swagger (OAS)

  • Cosa é =>
    • Open API specification è uno standard formale per descrivere quali servizi un server API espone.
    • Swagger (lo spaccone) è il nome meno formale.
  • Documentazione strutturata in blocchi
    • openapi
      • indico la versione di open API di riferimento per il documento che sto stilando
    • info
      • inserisco alcune info sul progetto
    • server 
      • indico le informazioni relative al server che espone le API che sto sviluppando
    • paths
      • indico i differenti paths dell’URL che ospitano le API
    • operation
      • indico gli endpoint delle mie API
    • parameters
      • in path

 

      • in query : aggiungo parametri nella query string

    • responses:
      • caso delete

                     

      • caso create (post)

                     

    • components/schemas
      • specifico la struttura dati referenziata nelle ‘operations’ (il modello dati esposto negli end point)

    • external documentation
      • è possibile aggiungere alla specificazione dei link a documentazioni esterne 
  • Esempio di swagger file (.yaml):

 

Consuming API Client-Side

  • <script>
    $(document).ready(function () {
    var table = $("#customers").DataTable({
    ajax: {
    url: "/api/customers/",
    dataSrc: ""
    },
    columns: [
    {
    data: "name",
    render: function (data, type, customer) {
    return "<a href='/customers/edit/" + customer.id + "'>" + data + "</a>";
    }
    },
    {
    data: "membershipType.name",
    },
    {
    data: "id",
    render: function (data) {
    return "<button class='btn btn-link js-delete'
    data-customer-id=" + data + ">Delete</button>";
    }
    }
    ],
    });

    $("#customers").on("click", ".js-delete", function () {
    var button = $(this);

    if (confirm("Are you sure you want to delete this customer?")) {
    $.ajax({
    url: "/api/customers/" + button.attr("data-customer-id"),
    method: "DELETE",
    success: function () {
    table.row(button.parents("tr")).remove().draw();
    }
    });
    }
    });
    });
    </script>

 

Minimal API (.NET 6) vs Controller API

  • Le funzionalità presenti in una Minimal API sono inizialmente limitate dopodiché possono essere estese attivamente secondo le crescenti necessità dell’applicazione.
  • Anche la struttura segue la stessa logica e nasce scarna per poi evolversi.
  • Sviluppare una Minimal API richiede un apprendimento iniziale meno importante.
  • Le performance di una Minimal API sono leggermente migliori.

 

Single Page Application (SPAs)

  • Tutte le View() (quindi l’intera pagina) sono generate lato client.
  • Non ci sono quindi comandi Razor.
  • Ajax (Asynchronous JavaScript and XML) => Quando un utente lancia un’evento, l’applicazione, invece di inviare una request al server per richiedere il markup per l’intera pagina, fa una richiesta ajax ad una API per ricevere i dati richiesti e generare la relativa vista lato client.
    • $.ajax(url) 
      .done(function(result){
      // Do something with the result
      });
    • Il risultato di una chiamata Ajax può essere
      • XML
      • HTML markup
      • JSON
    • Ajax fornisce un livello d’astrazione nell’utilizzare l’ogetto XMLHttpRequest presente nei browser più diffusi.
  • Single Page Application = > A livello tecnico si ha una sola pagina dove header e le colonne sono fisse mentre è la pagina centrale che cambia in funzione delle chiamate ajax richiesta dall’attività dell’utente => 
    • La client: Angular o Backbone.
    • La server: Asp.net WEB API.

 

Lavorare con le aree

  • Aggiungere un Area con il click destro sulla cartella Area e inputare il nome dell’Area
  • Aprire il file Global.asax.cs dentro il metodo Application_Start inserire AreaRegistration.RegisterAllAreas();
  • Aprire il file AreaRegistration.cs nella cartella Areas/ dove sono definite le route per l’Area e aggiungere la dichiarazione dello spazio dei nomi usato nei Controller dell’Area: namespaces: new[] { “.Areas..Controllers” }
  • Aprire il file RouteConfig.cs nella cartella App_Start principale e aggiungere nel MapRoute la dichiarazione dello spazio dei nomi usato nei Controller della sezione principale: namespaces: new[] { “.Controllers” }

 

Autenticazione e Autorizzazione

  • Principi generali autenticazione via token
    1. Porre la logica nel 
      • Middleware (MVC Core)
      • HttpHandler (ASP.NET)
      • ActionFilter (MVC 5)
    2. Il token deve essere criptato prima di essere inviato al client e decriptato prima di essere letto lato server
    3. I cookie dove viene memorizzato il token e tutti gli altri cookie devono avere una data di espirazione.
    4. E’ possibile riusare la struttura nativa dell’Identity framework o creare delle classi custom per persistere i dati di autenticazione nel DB.
  • In Asp.Net MVC ci sono 4 tipi di autenticazione possibili
    • Nessuna => Utenti anonimi posso accedere alla pagine della nostra applicazione
    • Account individuale => E’ l’opzione più comune e si basa sulla creazione di account (tramite form) legati ad un singolo utente. Con questa opzione è possibile attivare anche il logging tramite Social account.
    • Account di organizzazione =>  Permette di attivare un’autenticazione di tipo ‘Single Sing On’ per accedere ad applicazioni interne o cloud usando l’Active Directory.
    • Autenticazione Windows =>
      • E’ utilizzato nelle Intranet. Un utente che si logga nel SO del proprio computer nella rete aziendale avrà accesso alle applicazioni raggiungibili nella Intranet.
      • Implementata da IIS
  • ASP.NET Identity (former ASP.NET membership) =>
    • \\references
      Microsoft.AspNet.Indentity.Core
      Microsoft.AspNet.Indentity.EntityFramework
      Microsoft.AspNet.Indentity.Owin
    • L’architettura di ASP.NET Indentity è composta da 3 tipi di classi =>
      • Entities => Sono le entità  che costituiscono il dominio
        • IdentityUsers => Corrisponde agli utenti registrati nell’applicazione.
        • IdentityUserClaims =>
          • E’ un’insieme di coppie nome-valore detti claims che rappresentano l’identità di un utente.
          • E’ usata nell’autorizzazione basata sui claims.
        • IdentityUserLogins => Contiene informazioni sui provider esterni di autenticazione (es: Google, Facebook).
        • IdentityRoles=>
          • E’ una collezione di utenti (es: Admin, Developer, Manager).
          • E’ usata nell’autorizzazione basata sui ruoli.
        • IdentityUserRoles  => Contiene informazioni su quali ruoli sono assegnati a chi.
      • Managers =>  Sono le classi (API) che forniscono tutte le operazioni che si possono realizzare con l’ Identity Framework.
        • UserManager => Realizza le operazioni legate agli utenti interagendo direttamente con la classe UserStore.
        • RoleManager => Realizza le operazioni legate ai ruoli interagendo direttamente con la classe RoleStore.
        • SignInManger => Realizza le operazioni di autenticazione degli utenti.
      • Stores  =>
        • Sono le classi usate dai Managers per peristere e recuperare le Entities 
          • UserStore
          • RoleStore
        • Costituiscono il livello di persistenza dove i dati sono memorizzati.
        • ASP.NET Identity si basa sul EF Code First per fornire l’implementazione del livello persistenza.
        • Se si vuole è possibile non usare EF ma creare il proprio meccanismo di persistenza implementando le interfacce necessarie (es: database NoSql).
  • web.config
    • //Windows authentication
      <system.web>
      <authentication mode="Windows">
      <authorization>
      <deny users="?" />
      </authorization>
      </system.web>

      //Forms authentication
      <system.web>
      <authentication mode="Forms">
      <forms loginUrl="~/Login/Authenticate" timeout="2800"
      </authenticaton>
      </system.web>
  • Attributi per filtrare gli accessi =>
    • [Authorize], [Authorize(Roles = “role_name, role_name2”)] =>
      • Posso usare questo attributo come filtro se voglio restringere l’utilizzo della risorsa protetta ad utenti loggati. 
      • Può essere applicato a =>
        • Una singola azione.
        • Un’intero controller.
        • Intero progetto.
          • \App_start\FilterConfig.cs
            public class FilterConfig
            {
            public static void RegisterGlobalFilters(GlobalFilterCollection filters)
            {
            //aggiunge l'attributo [Authorize] a tutto il progetto
            //anche la home page
            filters.Add(new AuthorizeAttribute());
            }
            }
    • [AllowAnonymous] =>
      • Posso usare questo attributo come filtro se voglio permettere l’utilizzo della risorsa liberamente accessibile ad utenti non loggati (es: la home page).
      • Può essere applicato a =>
        • Una singola azione.
        • Un’intero controller.
  • Gestione di Utenti e Ruoli =>
    • User.IsInRole(“nome_ruolo”) =>
      • public ActionResult Index()
        {
        if (User.IsInRole("CanManageMovies"))
        return View("List");
        else
        return View("ReadOnlyList");
        }
    • Creare un nuovo ruolo e assegnarlo all’utente
      • /Controllers/AccountController.cs
        using Microsoft.AspNet.Identity.EntityFramework;

        var roleStore = new RoleStore<IdentityRole>(new ApplicationDbContext());
        var roleManger = new RoleManager<IdentityRole>(roleStore);
        await roleManger.CreateAsync(new IdentityRole("CanManagerMovies"));
        await UserManager.AddToRoleAsync(user.Id, "CanManagerMovies");
  • OAuth2 (Protocollo di autorizzazione aperta) =>
    • Cosa é:
      • Standard aperto di autorizzazione
      • Sviluppato da Twitter nel 2006
      • E’ ora uno standard industriale esistente per l’autorizzazione online, progettato per un sito Web o un’applicazione.
      • Autorizza temporaneamente una web app ad accedere a risorse ospitate da altre web app per conto dell’utente, limitando le azioni che tale app client è in grado di eseguire su tali risorse .
      • Tutte queste azioni vengono eseguite senza condividere le credenziali dell’utente.
      • Usato tra ll’altro per autorizzazione delle API
    • Come funziona (Il nostro sito vuole offrire la possibilità di loggarsi tramite Facebook) => 
      • Provider di autenticazione esterna:
        • Preventivamente è necessario registrare la nostra applicazione presso il provider di autenticazione esterna.
        • Ricevere i codici da utilizzare in tutto il processo di login.
          • codice applicazione {key}
          • codice segreto {secret}
      • Esempio di dialogo tra l’app client e il provider di autenticazione scelto:
        • L’utente del nostro sito clicca su login con Facebook.
        • Il sito invia a FB la richiesta di autenticazione:
          • Sito => {key, secret} => FB
        • FB conferma che l’autenticazione dell’utente è avvenuta con successo:
          • FB => {authorization token} => Sito
        • Il sito reinvia la richiesta di autenticazione con il token fornitogli da FB. Questo per evitare operazioni malicious 
          • Sito =>  {token, key, secret} => FB
        • Allora FB invia al sito un token per l’accesso. Con questo token è possibile accede ad una parte delle informazione del profilo dell’utente. 
          • FB => {access token} => Sito
        • Tutte le comunicazioni sono sicurizzate (https).
  • Social Login (usa OAuth2) =>
    • Per abilitare il social login per autenticarsi nella nostra applicazione è necessario:
      1. Abilitare SSL per creare un canale di trasmissione sicuro verso il login provider scelto.
        • 1- Da Visual Studio con il mouse andare sul progetto e fare F4 per accedere alla lista delle Proprietà.
          2- Impostare la proprietà 'SSL Enable' = true.
          3- Memorizzare l'indirizzo nella proprietà 'SSL URL' (https:\\..)
          3- Click dx sul progetto e aprire il pannello Proprietà
          4- Nel tab 'Web' inserire il nuovo indirizzo della nostra applicazione
          5- Disabilitare l'URL non sicurizzato andando \App_Start\FilterConfig.cs
          public class FilterConfig
          {
          public static void RegisterGlobalFilters(GlobalFilterCollection filters)
          {
          filters.Add(new RequireHttpsAttribute());
          }
          }
      2. Registrare la nostra applicazione presso il provider di autenticazione esterna scelto:
        • 1- Aprire developers.facebook.com
          2- Registrare un nuovo account
          3- Dal proprio profilo, aggiungere l'applicazione ('Add a new app') e scegliere come tipo website
          4- Indicare il nome della propria applicazione
          5- Creare un nuovo 'Facebook app ID'
          6- Indicare l'URL SSL della propria applicazione
          7- Nella Dashboard è possibile recuperare l'app Id {key} e l'app secret
          8- Tornare nella propria applicazione \App_start\Startup.Auth.cs e inserire la key e il secret nello spazio dedicato a Facebook
          public partial class Startup
          {
          public void ConfigureAuth(IAppBuilder app)
          {
          app.UseFacebookAuthentication(
          appId: "<my_app_id>",
          appSecret: "<my_app_secret>");
          }
          }
          9- Se vogliamo customizzare il form di login con dei dati in più rispetto dobbiamo modificare la View \View\Account\ExternalLoginConfirmation.cshtml
          e il relativo controller
          10- Autenticandosi con FB aggiungerà una linea nella tabella AspNetUserLogins per memotizzare l'accesso token fornito da FB per gestire
          la sessione che il nostro utente ha aperto sulla nostra applicazione.
  • Open ID Connect (OIDC) =>
    • Cosa é =>
      • Standard aperto di autenticazione
      • Sviluppato da OpenID Foundation nel 2014
      • E’ un protocollo di identità il cui compito è autorizzare e autenticare con OAuth 2.0 (evoluzione di OAuth 2.0)
      • Deve solo associarsi al proprio OpenID per condividere il nome e l’indirizzo email con i siti web che si visitano.
      • OpenID ti consente di controllare le informazioni che condividi con questi siti web. 
      • La password che si acquisisce con l’aiuto di OpenID viene fornita solo al proprio provider di identità che conferma la nostra identità al sito Web che stiamo visitando aumentando notevolmente la sicurezza
      • SSO
        • Può essere utilizzato anche per Single Sign On (SSO) tra le applicazioni.
        • L’utente può utilizzare un account esistente e utilizzare più siti Web senza la necessità di creare password.
      • E una potente alternativa a SAML 2.0 in qualche modo più complesso.
      • Utilizza un token Web JSON firmato e crittografico, che viene verificato per garantire che l’access token e l’identity token non abbiano interferito durante lo scambio di informazioni tra le parti.
    • Come funziona =>
      • Un OpenID Connect include tre parti:
        1. Resource owner
          • E’ l’utente finale che una client app
        2. Client app (es: Desktop app, Web app, Mobile app, Angular app, etc)
          • La client app può voler accedere ad una risorsa protetta in nome dell’utente finale che glielo ha chiesto
          • Per motivi di sicurezza il client non conosce e non deve conoscere user e password
          • Infatti non implementa nessun form di login, piuttosto reindirizzerà l’utente finale verso il login form fornitro dall’authentication server scelto
          • Trusted delegation => L’utente finale lascia agire la client app in suo nome
        3. Protected Resource (es Web API)
          • E’ la risorsa che l’utente finale vuole accedere tramite la client app
          • Deve decidere a quale authentication server fare affidamento 
        4. Authentication Server (Azure Active Directory, GitHub, Twitter)
          • Agisce da identity provider
          • E’ un server completamente indipendente e separato dal resto delle componenti che l’utente finale sta usando e vuole accedere
      • Esempio di workflow => 
        • Client app =>  Authentication server
          • La client app reindirizza l’utente finale verso l’authentication server 
        • Authentication server
          • Direttamente sull’authentication server l’utente finale si autentica (es: via sms, via impronte digitali, riconoscimento facciale,  via user e psw)
        • Authentication server => Utente finale
          • Token
            • Dopo che l’utente finale si è autenticato su di esso, l’authentication server genera un token.
            • Il token contiente informazioni sull’utente finale (resource owner), i suoi claims (nome, cognome, email…) ed è digitally signed.
            • Il token è creato esplicitamente per la risorsa che l’utente finale (via la client app) vuole accedere e a richiesto.
        • Utente finale =>  Client app
          • L’utente finale conscegna il token alla client app.
        • Client App => Protected resource 
          • Ora, dopo aver ricevuto il token, il client può eseguire un’azione per conto dell’utente finale
          • Invia il token alla protected resource per potervici accedere.
        • Protected resource
          • Verifica che il token arrivi dalla client app in questione
          • Verifica che l’utente finale sia effettivamente lui 
          • Verifica che il token sia creato per accede esplicitamente ad essa

    • Azure Active Directory 
      • Tramite l’active directory di Azure è possibile implementare il protocollo Open Id connect 
      • Azure Active Directory (AAD) = Auth Server  
      • Nell’active directory è necessario registare sia la ‘protected resource’ che l’app client che vi ci accede

 

Ottimizzazione delle performance

  • Ottimizzare Client tier (Browser)
    • Regola generale => ridurre il numero di richieste client-server (essendo client e server geofraficamente distanti).
    • Sviluppo API => Implementare oggetti DTO ad hoc e leggeri per evitare di esporre proprietà non necessarie.
    • Usare Bundle per ridurre il peso di caricare file .js e .css. e posizionarli in ultima posizione nel file html vicino al tag </body> così da permettere all’intera pagina di caricarsi prima di aspettare il caricamento delle risorse esterne .js e .css e offrire agli utenti un’esperienza visiva migliore.
      • Il bundle è normalmente attivo solo nel release build
        • /Web.config
          //Per rendere il bundle (cioè il raggruppamento all'apertura dell'applicazione web del download di file .js e .css) è necessario impostare debug="false"
          <system.web>
          <compilation debug="false" targetFramework="4.5"></compilation>
          </system.web>
  • Ottimizzare Application tier (IIS)
    • Output Caching =>
      • E’ possibile attivare un meccanismo di caching per prevenire che una ennesima richiesta http ad una stessa risorsa interroghi il server.  Tale richiesta leggerà la cache invece che interrogare il server.
      • [OutputCache] => E’ possibile attivare il caching tramite questo attribute applicato ad una singola Action o ad un’intero Controller. 
        • //la cache dura 50 secondi
          //la cache può essere lato client o lato server
          //se l'azione ha 1 o più parametri è possibile creare una nuova versione
          //della pagina da mettere in cache ad ogni nuovo valore di tale parametro
          [OutputCache(Duration = 50,
          Location = OutputCacheLocation.Server
          VaryByParam = "param_name")]
          public ActionResult Index()
          {
          }
      • Disabilitare caching per una data azione
        • //Crea una nuova versione della pagina in cache ad ogni nuovo valore di ogni 
          //parametro dell'azione che corrisponde a non avere alcuna cache. Ogni richiesta
          //causerà una nuova interrogazione verso il server
          [OutputCache(Duration = 0, VaryByParam = "*")]
          public ActionResult Index()
          {
          }
    • Data Caching =>
      • using System.Runtime.Caching;

        if(MemoryCache.Default["Genres"] == null)
        {
        MemoryCache.Default["Genres"] = _context.Genres.ToList();
        }
        var genres = MemoryCache.Default["Genres"] as IEnumerable<Genre>;
    • Usare la release build
    • Disabilitare la gestione delle sessioni (e mantenere la nostra applicazione stateless) =>
      • /web.config
        <system.web>
        <sessionState mode="Off"></sessionState>
        </system.web>
  • Ottimizzare Data Tier (Sql server)
    • Schema => Risolvere problemi legati allo schema del DB verificando:
      • Chiavi primarie.
      • Chiavi esterne.
      • Indici (per filtrare le query).
    • Query => Risolvere problemi legati alle query 
      • Dare un occhio alle query generate da EF e rimpiazzare le query è troppo complesse con stored procedure.
      • Execution plan
      • Creare tabelle di sola lettura (e mantenerle sincronizzate con le altre)

 

Deployment

  • Publish (Click dx sul progetto => Click Publish) => 
    • Scegliere il Profilo (le volte successive può essere richiamato).
      • Azure Website
      • Import (se esiste un profile publico da usare fornito dall’Interner provider)
      • Custom
      • //Nella seguente cartella del progetto c'è una lista di file che corrispondo
        //ai profili di deploy che abbiamo creato
        \Properties\PublishProfiles\<my_profile1>.pubxml
        <my_profile2>.pubxml
    • Scegliere il  Metodo =>
      • Deploy via FTP
      • Creazione nel File system
      • Deploy via Web (raramente usato, IIS deve autorizzarlo)
    • Deploy del Database =>
      • Caso automatico => Scegliendo il metodo Web il deploy del DB può essere gestito automaticamente.
      • Caso manuale => Scegliendo come metodo FTP, File system allora si crea manualmente uno script da dare al DBA.
        • update-database -script -SourceMigration:<nome_della_migrazione_da_cui_iniziare>

          Lo script generato gestisce
          - modifiche allo Schema
          - modifica ai dati

          Per sapere qual'è l'ultima migrazione lanciata nel nostr DB di produzione leggere la tabella __MigrationHystory
  • Build configuration =>
    • E’ possibile parametrizzare alcuni settaggi per gestire il deploy in diversi ambienti (test, staging, prod).
    • Aggiungere una configurazione
      • Aprile ‘Build’ => scegliere l’opzione ‘Configuration Manager’
      • Creare la nuova configurazione
      • Copiare il parametri della configurazione ‘Release’
    • Creare un nuovo file di trasformazione (xslt)
      • Click dx su web.config => Add config transform 
      • Modificare il file aggiungendo le configurazioni che si vogliono customizzare e applicare le trasformazioni xslt.   
        • \\Esempio nel file originale web.config (usato in DEV) abbiamo aggiunto \\un'application setting per gestire il MailServer.
          \\Nel file web.<my_profile>.config =>
          <appSettings>
          <add key="MailServer" value="prod-smtp.com"         xdt:Transform="SetAttributes" xdt:Locator="Match(key)"/>
          </appSettings>
        • Per vedere il preview della trasformazione xslt:
          • Click dx sul web.<my_profile>.config => Preview Transform
  • Application settings => E’ possibile aggiungere nel file web.config delle coppie chiave, valore da usare nel nostro codice e che posso aspitare dei valori diversi nei diversi ambienti in cui si fa il deploy:
    • //Possiamo ospitare ad esempio le credenziali per gestire il login via Facebook
      web.config
      <appSettings>
      <app key="FacebookAppId" value="12345">
      <app key="FacebookAppSecret" value="abcde">
      </appSettings>

      //Nel codice è possiile richiamare questi valore
      using System.Configuration
      var facebookAppId = (int)ConfigurationManager.AppSettings["FacebookAppId"];
  • Criptare i file di config
    • Per delle ragioni di sicurezza è possibile separare le sezioni <appSettings> e <connectionString> estraendole dal file web.config e mettendole in 2 nuovi file .config 
    • Lanciare il tool di Visual Studio per criptare/decriptare i 2 nuovi file .config.
      • aspnet_regiis -pef or -pdf

 

Filters

  • In ASP.NET MVC supporta 4 tipi di filtri:
    • Authorization filters =>
      • Implementano l’attributo IAuthorizationFilter.
      • Sono eseguiti prima di ogni altro filtro
      • Esempio:
        • [Authorize]  
          public ActionResult Secured()
          {
          return View();
          }
    • Action filters =>
      • Implementano  l’attributo IActionFilter.
      • Sono eseguiti prima o dopo un’azione
      • Esempio:
        • E’ possibile modificare i viewdata ritornati da un controllo
    • Result filters =>
      • Implementano l’attributoIResultFilter.
      • Sono eseguiti prima o dopo che un ViewResult sia eseguito
      • Esempio: 
        • E’ possibile modificare il risultato di una vista subito prima che la vista venga renderizzata al browser
    • Exception filters =>
      • Implementano l’attributo IExceptionFilter
      • Sono eseguiti come ultimi
      • Esempio:
        • E’ possibile utilizzare i filtri delle eccezioni  per gestire gli errori generati dalle actions e dai results.
        • E’ possbile utilizzare i filtri delle eccezioni per registrare gli errori.
  • Inline Filters =>
    •  
  • Customized filters =>
    • E’ possibile creare dei filtri customizzati tramite la classe ActionFilterAttribute (che eredita da IActionFilter e IResultFilter)
    • public class LogActionFilter : ActionFilterAttribute
      {
      public override void OnActionExecuting(ActionExecutingContext filterContext)
      {
      Log("OnActionExecuting", filterContext.RouteData);
      }

      public override void OnActionExecuted(ActionExecutedContext filterContext)
      {
      Log("OnActionExecuted", filterContext.RouteData);
      }

      public override void OnResultExecuting(ResultExecutingContext filterContext)
      {
      Log("OnResultExecuting", filterContext.RouteData);
      }

      public override void OnResultExecuted(ResultExecutedContext filterContext)
      {
      Log("OnResultExecuted", filterContext.RouteData);
      }


      private void Log(string methodName, RouteData routeData)
      {
      var controllerName = routeData.Values["controller"];
      var actionName = routeData.Values["action"];
      var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName);
      Debug.WriteLine(message, "Action Filter Log");
      }

      }

 

Exception Handling

  1. Usando l’attribute Filter [HandlerError] =>
    • //E' possibile abilitare la visualizzazione di un messaggio customizzato in caso di exception lanciata durante l'esecuzione del codice

      - Passo1 =>
      - Applicare il Filter a livello globale => nel file \App_stat\FilterConfig.cs aggiungere

      public class FilterConfig
      {   
      public static void RegisterGlobalFilters(GlobalFilterCollection filters)  
      {      
      //aggiungendo questo comando, attivo la gestione customizzata
      //della visualizzazione degli errori

      //Se si vuole visualizzare un messaggio diverso per un tipo di errore
      HandleErrorAttribute a1 = new HandleErrorAttribute();
      a1.ExceptionType = typeof(DivideByZeroException);
      a1.View = "DivideError";
      filters.Add(a1);

      HandleErrorAttribute a2 = new HandleErrorAttribute();
      a2.ExceptionType = typeof(NullReferenceException);
      a2.View = "NullError";
      filters.Add(a2);

      //Visualizzo la pagina \Views\Shared\Error.cshtml per tutte le altre
      //eccezioni per cui non vogliamo una pagina di errore customizzata
      filters.Add(new HandleErrorAttribute());   
      }
      }

      - Applicare il Filter a livello di Controller o Action

      [HandleError("my message error!")]
      [HandleError(ExceptionType=typeof(DivideByZeroException), View="DivideError"]
      [HandleError(ExceptionType=typeof(NullReferenceException), View="NullError"]
      public ActionResult GetCustomer()
      {
      //....
      }

      - Passo2 => L'html visualizzato è presente in questo file

      \Views\Shared\Error.cshtml

      Eventualmente posso tipare questa pagina con
      @model HandleErrorInfo
      e visualizzare il messaggio di errore
      @Model.Exception.Message

      - Passo3 => nel file \web.config aggiungere attivo Exception Filters

      <system.web>
      <customErrors mode="On | RemoteOnly">
      <error statusCode="404" redirect="~/404.html">
      </customErrors>
      </system.web>
  2. Customizzando l’attribute Filter [HandleError] => 
    • //Posso anche sovrasrivere l'attributo [HadlerError] per inserire della logica
      //ad esempio un meccanismo di log
      public class MyCustomExFilter: HandlerErrorAttribute
      {

      protected override void OnException(ExceptionContext filterContext)
      {

      ViewResult vr = new ViewResult();
      v.ViewName = "Error";
      filterContext.Result = v;
      filterContext.ExceptionHandled = true;

      //inerisco loggging degli errori
      Exception e = filterContext.Exception;
      }
      }

      //applicarlo localmente
      [MyCustomExFilter]
      public ActionResult GetCustomer()
      {
      //....
      }

      //applicarlo globalmente
      Applicare il Filter a livello globale => nel file \App_stat\FilterConfig.cs aggiungere

      public class FilterConfig
      {   
      public static void RegisterGlobalFilters(GlobalFilterCollection filters)  
      {      
      //aggiungendo questo comando, attivo la gestione customizzata
      //della visualizzazione degli errori

      filters.Add(new HandleErrorAttribute(new MyCustomExFilter()));   
      }
      }
  3. Sovrascrivendo OnException in un BaseController o in un Controller:
    • //E' possibile usare un controller base e centralizzare la logica di exception handling

      //All'interno di un base controller così da poter riutilizzare tale logica in ogni controller
      public class BaseController : Controller
      {
      protected override void OnException(ExceptionContext filterContext)
      {
      ViewResult vr = new ViewResult();
      v.ViewName = "Error";
      filterContext.Result = v;
      filterContext.ExceptionHandled = true;
      }
      }

      public class CustomerController: BaseController
      {
      }

      //html visualizzato è presente in questo file:
      //\Views\Shared\Error.cshtml
  4. Tramite IIS => 
    • //Erore Http 404 => In questo caso non si tratta di un errore generato dall'applicazione e esula dal mecanisco di catchng degli errori 
      //abilitato nel passo precedente. 
      //Tale errore è gestito direttamente da IIS, infatti in tal caso la richiesta http non arriva neanche alla ASP.NET MVC engine.
      //E' possibile indicare esplicitamente un file per gestire la visualizzazione di tale

      \web.config
      //gestisco gli errori HTTP lanciati dell'applicazione
      <system.web>
      <customErrors mode="On | RemoteOnly">
      <error statusCode="404" redirect="~/404.html">
      </customErrors>
      </system.web>

      //gestisco gli errori HTTP dovuti a richieste di le risorse statiche (come le //immagini) non esistenti
      <system.webServer>
      <httpErrors mode="On | RemoteOnly">
      <remove statusCode="404">
      <error statusCode="404" path="404.html" responseMode="File">
      </httpErrors>
      </system.webServer>

 

MVC 5 VS MVC Core

  • CORE
    • è cross-platform
    • è più veloce
    • è più simplificato
    • è cloud ready
    • è possibile sviluppare e gestire applicazioni .NET Core senza Visual Studio =>
      • DNVM => .NET version manager (permette di scaricare la corretta versione .NET Core per il nostro sistema operativo)
        • //powershell
          dnvn list
      • Project.json / deps.json =>
        • Sostituisce  il file .csproj (XLM file) usato da .NET Framework
        • Specifica le dependency che l’applicazione .NET Core usa.
        • A partire da tale file ne viene creato un secondo con project.lock.json che è quello dove sono speficicate le versioni effettivamente usate 
      • DNU (Development Utilities) => Permette la compilazione e recupera le referenze  (presenti nel file json specifico)
        • //aggiorna le dependency necessarie (specificate nel file project.json)
          //nella cartetta /packages
          dnu restore
          //faccio il build del mio programma
          dnu build
        • La compilazione crea i file .ddl ma non il file .EXE 
      • DNX => Esegue i file compilati (runtime environment che annulla l’esigenza di avere un file .exe perché appunto esegue i file compilati direttamente runtime)
        • //esegue il programma una volta compilato
          dnx run
    • .NET Core non usa la GAC (Global Assembly Cache) che è specifica di Windows
      • GAC => Cartella nella file system dove sono memorizzati (registrati) .NET assembly (.dll) progettati per essere condivisi da tutte le applicazioni .NET eseguite sulla macchina

  • Configuration => In CORE non è possibile legarsi al file web.config in quanto le applicazioni sviluppate sono pensate per essere deployate in ogni tipo di web server (es: Apache)
    • MVC => web.config
    • CORE  =>  appsetting.json
      • Settare properties
        • Click dx sul file, aprire il pannello delle proprietà e verificare che il settaggio sia il seguente

 

      • //Come leggere il valore di una proprietà di configurazione presente nel file appsetting.json
        public class CustomerController : Controller
        {
        public CustomerController(IConfiguration configuration)
        {
        string str = configuration["Environment:Prod"];
        }
        }

        oppure .NET 6.0

        builder.Configuration["Logging:LogLevel:Default"];
      • E” possibile utilizzare diversi tipi di sorgenti per memorizzare le nostre config 
        • XML
        • File
        • Variabile d’ambiente
  • Startup => 
    • MVC 5 => se è necessario eseguire codice all’avvio dell’app ASP.NET MVC , in genere verrà utilizzato uno di questi approcci
      • Application_Start (global.axa) =>
        • Ospitate in IIS, le app ASP.NET MVC 5 si basano su IIS per creare un’istanza di determinati oggetti e chiamare determinati metodi
        • Prima di gestire la prima richiesta ricevuta in assoluto, ASP.NET crea un’istanza della classe del file Global.asax (che deriva da HttpApplication).
        • Chiama il metodo Application_Start presente nella classe del file Global.asax.
        • Qualsiasi logica che deve essere eseguita all’avvio dell’app ASP.NET MVC può essere aggiunta a questo metodo.
      • Folder App_Start =>
        • Molti pacchetti NuGet per ASP.NET MVC e API Web usano il pacchetto WebActivator per consentire loro di eseguire del codice durante l’avvio dell’app.
        • Di solito questo codice viene aggiunto alla cartella App_Start e viene configurato per essere eseguito immediatamente prima o subito dopo Application_Start (tramite un attributo).
      • OWIN e Project Katana con ASP.NET MVC. =>
        • In tal caso, l’app includerà un file Startup.cs responsabile della configurazione del middleware delle richieste in un modo molto simile al comportamento di ASP.NET Core.
      • Startup class  (startup.cs) =>
        • ConfigureServices method => si configurano le dependency injenction da utilizzare nell’applicazione
        • Configure method => aiuta a configurare i servizi Middleware

 

    • CORE => 
      • Program.cs e startup.cs 

 

        • Fin dall’inizio, ASP.NET Core ha separato il bootstrap dell’ “host” da quello dell’applicazione.
        • Cio’ si é manifestato con la separazione dello startup in 2 file tradizionalmente chiamati program.cs e startup.cs 
        • E’ differente lo scope 
          • Program.cs => Si occupa della configurazione dell’infrastruttura che in genere rimarrà stabile per tutta la durata del progetto
          • Start.cs => Tale file verrà modificato spesso per aggiungere nuove funzionalità e aggiornare il comportamento dell’applicazione.
      • Program.cs =>. 
        • Le app ASP.NET Core sono programmi autonomi e in genere includono un file Program.cs contenente il punto di ingresso per l’app 
        • In .NET 6, questo file è semplificato dall’uso di istruzioni implicite e di primo livello, eliminando la necessità di molto codice “boiler plate”.
        • //E' nesarrario attivare ogni componente che si vuole utilizzare. .Net Core nasce più leggero possibile 

          //PASSO 1 => utilizzo un builder per configurare l'host e i suoi servizi (classe WebApplicationBuilder)
          -------------------------------------------------------------------------------------------------------------------- var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddRazorPages(); var logLevel = builder.Configuration["Logging:LogLevel:Default"]; --------------------------------------------------------------------------------------------------------------------

          //PASSO 2 => creo la pipeline delle richieste per l'app per parametrare la gestione di ogni HttpRequest (classe WebApplication)
          --- ------------------------------------------------------------------------------------------------- var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapRazorPages(); app.Run();
          --------------------------------------------------------------------------------------------------------------------
      • Configurare un’applicatione .NET Core
        • (ASP.NET Core 2.x) WebHost.CreateDefaultBuilder()
          • Allo startup dell’applicazione vengono gestire solo le attività per settare la comuncazione web/HTTP
        • (.NET Core 3.x and .NET 5) Host.CreateDefaultBuilder()
          • Allo startup dell’applicazione viene aggiunto il supporto per
            • Long running “worker services” (es: leggere i messaggi da una queue)
            • gRPC services
            • Windows Services
          • Lo scopo con questa versione é stato di condividere la base del framework già utilizzata per fare il build dell’applicazione web (configuration, logging, DI) per aggiungere il supporto ad altri tipi applicazioni
          • Viene introdotto l’ endpoint routing
            • La gestione del routing non é più in fondo al middleware ma ha uno step del middleware dedicato.
            • E’ possibile ad esempio gestire il CORS.
          • Program.cs
            • public class Program
              {
              public static void Main(string[] args)
              {
              CreateHostBuilder(args).Build().Run();
              }

              public static IHostBuilder CreateHostBuilder(string[] args) =>
              Host.CreateDefaultBuilder(args)
              .ConfigureWebHostDefaults(webBuilder =>
              {
              webBuilder.UseStartup<Startup>();
              };

              }
        • (.NET 6) WebApplication.CreateBuilder()
          • In .Net 6 Il bootstrap é completamente rinnovato.
          • Vengono semplificate molte attività, introducendo la classe WebApplicationBuilder
          • var builder = WebApplication.CreateBuilder(args)

            1- Configura l'app affinché usi Kestrel come webserver
            2- Specifica di usare la cartella del progetto come cartella di root dell'applicazione
            3- Permette la lettura dei file di configurazione appsettings.json and appsettings.{env}.json
            4- Set Local user secrets storage only for the development environment
            5- Configurare le variabili di ambiente per consentire impostazioni specifiche del server
            6- Configura gli argomenti della riga di comando (se presenti)
            7- Configura il logging per leggere nel file appsettings.json (sezione 'logging') e accedere alla finestra Console e debug.
            8- Configura l'integrazione con IIS
            9- Configurare il service provider predefinito.
          • builder.Configuration:
            • Una raccolta di provider di configurazione per l’applicazione da comporre
            • Ciò è utile per aggiungere nuove origini e provider di configurazione.
            • E’ possibile una classe con la stessa struttura del file appsettings.json poi leggerla nel seguente modo:
            • var builder = WebApplication.CreateBuilder(args);

              var myAppSettings = builder.Configuration.Get<MyAppSettings>();
              string logLevel = myAppSettings.Logging.LogLevel.Default;

              oppure

              builder.Configuration["ServiceUrls:ProductAPI"];
          • builder.Services:
            • E’ utile per registrare le dependency injection.
            • E’ utile per aggiungere servizi forniti dall’utente o forniti dal framework.
            • var builder = WebApplication.CreateBuilder(args);

              builder.Services.AddHttpClient<IProductService, ProductService>();
              builder.Services.AddHttpClient<ICartService, CartService>();
          • Logging :
            • Una raccolta di providers per il logging per l’applicazione da comporre. E’ utile per aggiungere nuovi logging provider.
        • builder.Build()
          • app.Environment:
            • Fornisce informazioni sull’ambiente di esecuzione di un’applicazione (es: sviluppo o produzione).
        • .NET Generic host (HostBuilder): 
          • Il ‘Generic host’ ha sostituito il web host cosi’ che possa essere usato indipendentemente per applicazioni web, applicazioni windows, linux e console.
          • L’Host é un ‘container’ che offre una ricca varietà di servizi built-in.
          • L’ Host é responsabile al minimo di:
            • Configurare un server (HTTP Server implementation) 
            • Configurare la pipeline di processo delle richieste.
            • Gestire il ciclo vitale dell’applicazione (startup, shotdown).
          • L’ Host é responsabile opzionalmente di
            • Dependency Injection
            • Configuration
            • Logging
          • Per compilare dopo la configurazione, chiama Build(). 
            • IHost host = Host.CreateDefaultBuilder(args)
              .UseSerilog((context, loggerConfiguration) =>
              {
              loggerConfiguration.ReadFrom.Configuration(context.Configuration);
              })
              .Build();
              await host.RunAsync();

              oppure

              IHost host = Host.CreateDefaultBuilder(args)
              .ConfigureServices(
              (hostContext, services) => { services.AddHostedService<Worker>(); })
              .Build();
              host.Run();
        • IHostedService  =>
          • E’ un servizio che viene eseguito in background (non offre un’interfaccia)
          • Le app potrebbero avere altro codice che deve essere eseguito all’avvio dell’app.
            • Tale codice viene in genere inserito ancora in Program.cs.
            • oppure registrato come IHostedService e verrà eseguito dal .NET Genercic Host all’avvio dell’app.
          • //L'interfaccia IHostedService espone solo due metodi
            //Si registra l'interfaccia durante la configurazione dei servizi dell'app e l'host fa il resto
            //chiamando il metodo StartAsync prima dell'avvio dell'app
            public class LongRunningTaskService : IHostedService
            {
            public Task StartAsync(CancellationToken cancellationToken)
            {
            // Start the work
            }

            public Task StopAsync(CancellationToken cancellationToken)
            {
            //Graceful shutdown from the Host
            }
            }
      • Program.cs + Statup.cs  =>  E’ possibile aggiunge la classe Startup ad un progetto ASP.NET Core, spostando la logic da program.cs al nuovo file statup.cs
        • //Startup.cs
          //Creare la classe Statup
          - spostare le chiamate ai servizi/containers da Program.cs al methodo ConfigureServices()
          - spostare la middleware pipeline Program.cs al methodo Configure()
          public class Startup {

          public IConfiguration configRoot {
          get;
          }

          public Startup(IConfiguration configuration) {
          configRoot = configuration;
          }

          public void ConfigureServices(IServiceCollection services) {
          services.AddRazorPages();
          services.AddMvc();
          }

          public void Configure(WebApplication app, IWebHostEnvironment env) {
          if (!app.Environment.IsDevelopment()) {
          app.UseExceptionHandler("/Error");
          app.UseHsts();
          }

          app.UseHttpsRedirection();
          app.UseMvcWithDefaultRoute();
          app.UseStaticFiles(); // Per visualizzare file presenti nelle cartelle del progetto
          app.UseRouting();
          app.UseAuthorization();
          app.MapRazorPages();
          app.UseMvc(routes => {
          routes.MapRoute(
          name: "default",
          template: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index"});
          }); // Mi permette di usare le pagine RAZOR
          app.Run();

          }
          }

          //Program.cs
          //Modificare la pagine Program.cs - Eliminare le chiamate ai servizi/container e la pipeline del middleware dal file - Chiamare la nuova classe Startup var builder = WebApplication.CreateBuilder(args); var startup = new Startup(builder.Configuration); startup.ConfigureServices(builder.Services); // calling ConfigureServices method var app = builder.Build(); startup.Configure(app, builder.Environment); // calling Configure method
  • Controller Response
    • //ASP.NET MVC 5
      - MVC : Controller    => return ActionResult (Razor Views)
      - API : ApiController => return IHttpActionResult (JSon objects) 
      
      //ASP.NET Core
      - MVC : Controller =>     return IActionResult oppure Task<IActionResult> (Razor Views or JSon objects)
      - API : ControllerBase => return IActionResult oppure Task<IActionResult> (Razor Views or JSon objects) 
  • Controller Dispose
    • //ASP.NET MV5
      public class MovieController : Controller
      {
         protected override void Dispose(bool disposing)
         {
            base.dispose(disposing);
         }
      }
      
      //.NET CORE
      .. i controller non necessitano di un metodo depose esplicito
  • Controller Route
    • //ASP.NET MV5
      - MVC Controller (App_Start/RouteConfig.cs) => routes.MapRoute
      - API Controller (App_Start/WebApiConfig.cs) => routes.MapHttpRoute
      
      //.NET Core
      - MVC + API Controller (/Program.cs) => app.MapControllerRoute
  • Dependency Injection
    • MVC 5 => Necessità di librerie di terze parti
    • CORE => Supporto nativo (built-in)
      • Come implementarla =>
        • Implementarla nella routine ConfigureServices nel file startup.cs usando services.AddTransient, services.AddScoped, services.AddSingleton
        • //startup.cs
          //use this method to add services to the container
          public void ConfigureServices(IServiceCollection services)
          {
          services.AddScoped<CustomerDbContext, CustomerDbContextNewVersion);
          //AddTransient
          //AddSingleton

          }
        • Esempio di injection del DbContext:

        • Esempio di injecton di classe Repository

      • AddScoped =>
        • Inietta una sola Istanzia di un oggetto per tutta l’applicazione ad ogni nuova HTTP Request
          • Le istanze iniettate dal meccanismo della DI per gli oggetti obj e obj1 saranno le stesse e verranno rinnovate ad ogni nuova HTTP Request che arriva all’HomeController

        • Si comporta come AddSingleton ma solo all’interno di una singola richiesta HTTP
      • AddTransient =>
        • Inietta una nuova istanza per ogni DI oggetto richiesto in ogni parte dell’applicazione
          • Le istanze iniettate dal meccanismo della DI per gli oggetti obj e obj1 saranno diverse 
        • opposto di AddSingleton
      • AddSingleton =>
        • Inietta una sola istanza di un DI oggetto per l’intera vita dell’applicazione
          • Le istanze iniettate dal meccanismo della DI per gli oggetti obj e obj1 saranno sempre la stessa.

        • Opposto di AddTransient
      • Factory =>
        • Creo un punto centralizzato dove Intercetto le HTTP Request e analizzando i dati posso decidere quale istenze concrete ininiettare  
        • //statup.cs or program.cs
          public void ConfigureServices(IServiceCollection services)
          {
          //
          services.AddSingleton<IEventsRepository, EventsRepository>();

          //
          services.AddScoped<Customer>((ctx) =>
          {
          //factory code
          IHttpContextAccessor con =
          ctx.GerService<IHttpContextAccessor>();
          double amt = con.HttpContext.Request.Form["CustomerAmount"];
          if(amt > 100)
          {
          return new GoldenCustomer();
          }
          else
          {
          return new DiscountedCustomer();
          }
          }
          }

          //CustomerController
          public IActionResult Add([FromServices] Customer obj)
          {
          ...
          }
      • Visual Studio Debug – Mark Object ID
        • Durante il debug in Visual Studio è possibile visualizzare un Identificativo univoco assengnato ad un oggetto 
        • Tale possibilità ci permetterrà ad esempio di verificare che una DI di tipo AddScoped restituisca effettivamente lo stesso ID in ogni classe in cui l’oggetto è utilzzato 
  • Server host
    • MVC 5 => opsitate da IIS
    • CORE => è self hosting (KESTREL)
  • Static content
    • MVC 5 => In genere i file statici risiedono direttamente nell’app
    • CORE =>
      • Per gestire i file statici da un’app ASP.NET Core, è necessario configurare il middleware dei file statici
      • Con il middleware di file statici configurato, un’app ASP.NET Core servirà tutti i file che si trovano in una determinata cartella chiamata webroot (in genere /wwwroot, può essere cambiata)
      • Nessun file (non presente in /wwwroot) rischia di essere esposto accidentalmente dal server.
      • Non è necessario configurare alcuna restrizione speciale basata su nomi di file o estensioni, come nel caso di IIS
      • Il file /wwwroot/images/file1.jpg sarà visibile all’indirizzo => https://<hostname>/images/file1.jpg
  • Development
    • MVC 5 => Solo in windows
    • CORE => Anche in MAC o in LINUX usando VS Code
  • Middleware =>
    • Context = HTTP Request  (request pipeline) + HTTP Response (response pipeline) 
    • Cosa é =>
      • In ASP.NET Core è stato introdotto il concetto di Middleware
      • Non è altro che un componente (class) che viene eseguito ad ogni richiesta ricevuta da un’applicazione ASP.NET Coore
      • Tale classe sostituisce le classi dell’ASP.NET classico
        • HttpHandlers 
        • HttpModules 
    • Ciclo di una HTTP Request MVC:
      • L’utente invia una HTTP Request
      • Il webserver (IIS) alloca un thread (preso dal pool di thread preventivamente assegnato al sito) alla richiesta
        • Il web server ha un’esecuzione implicitamente in parallelo
      • Il controller la intercetta
      • Vengono pescati i dati dal Model
      • Vengono visualizzati i dati dal View e inviata una HTTP Response all’utente

    • Ordine del middleware
      • Esiste un ordine di esecuzione del middleware quando viene ricevuta una Http Request.
      • Verrà eseguito l’ordine opposto all’invio della Http Response.

      • L’ordine di esecuzione del middleware mostrato nell’immagne sopra equivale al seguente codice in program.cs (come consigliato da Microsoft):

    • Multi-componenti =>
      • In ASP.NET Core ci possono essere molteplici componenti che fanno parte del middleware
        • Middleware forniti dal .NET CORE framework
        • Middleware aggiunti via NuGet
        • Middleware Customizzati
    • Ciclo di eseguizione dei componenti nel middleware =>

 

    • Il middleware permette di eseguire una logica prima del normale ciclo del processo (pre-processing) prima cioè che la HTTP Request arrivi al Controller

    • Come implementarlo =>
      1. Usando labda expression o anonymous method direttamente nel file program.cs
      2. Creando una classe
        • Aggiungere al progetto una classe tipo MiddleWare (es: MyMiddleware.cs) usando il template fornito da Visual Studio
        • Scrivere la logica pre-processo nel metodo Inkove presente nella classe appena creata:
          • public class MyMiddleware
            {

            private readonly RequestDelegate _next;
            private readonly ILogger _logger;

            public MyMiddleware(RequestDelegate next, ILoggerFactory logFactory)
            {
            _next = next;
            _logger = logFactory.CreateLogger("MyMiddleware");
            }


            public async Task Invoke(HttpContext httpContext)
            {
            //Log
            _logger.LogInformation("MyMiddleware executing...");

            //insert pre-processing logic here
            //es:
            await httpContext.Response.WriteAsync("ddddd");
            ....
            ...

            //continuing processing
            if(_next != null)
            await _next.Invoke(httpContext);
            }

            }
    • Connettere il middleware alla normale pipeline di comunicazione:
      • Usando l’extension method:  
        • // Creare il relativo l'extension method per aggiungere il custom middleware appena creato all' HTTP Request Pipeline.
          public static class MyMiddlewareExtensions
          {
          public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
          {
          return builder.UseMiddleware<MyMiddleware>();
          }
          }


          //Statup.cs
          public void Configure(IApplicationBuilder app, IHostingEnvironment env)
          {
          app.UseMyMiddleware();
          }

          oppure

          //Program.cs
          app.UseMyMiddleware();
      • Senza usare l’exention method:
        • //startup.cs 
          //use this method to configure the http request pipeline
          public void Configure(IApplicationBuilder app, IHostingEnvironment env)
          {
          app.UseMiddleware<MyMiddleware>();
          }
    • Comandi per customizzare la pipeline =>
      • E’ possibile customizzare la pipeline in funzione della HTTP Request ricevuta
      • Run
        • È un modo abbreviato per aggiungere middleware alla pipeline che non chiama nessun altro middleware accanto.
        • Restituisce immediatamente la risposta HTTP (SALTA IL RESTO DEL MIDDLEWARE)
        • Attenzione: E’ consigliato usarlo in fondo alla pipeline.
        • public void Configure(IApplicationBuilder app)
          {
          app.UseMvc();

          app.Run(async context =>
          {
          await context.Response.WriteAsync("Return From Run.");
          });

          app.Run(async context => {
          await context.Response.WriteAsync("This is second Run.");
          });
          }

          //Il secondo Run presente nell'esempio non viene eseguito perché il primo ritorna immediatamente la risposta HTTP al client
          //Output
          Return From Run.
      • Use
        • Come Run ma offre la possibilità di indicare il next invoker, in modo che la richiesta HTTP venga trasferita al middleware successivo dopo l’esecuzione dell’Use corrente. 
        • Se non é indicato il next invoker. Use = Run
        • public void Configure(IApplicationBuilder app)
          {
          app.UseMvc();
          app.Use(next => async context =>
          {
          await context.Response.WriteAsync("Return From Use.");
          await next.Invoke(context);
          });
          app.Run(async context => {
          await context.Response.WriteAsync("This is from Run.");
          });
          }

          //Use viene eseguito all'interno della middleware e esegue il comando successivo
          //Output
          Return From Run.This is from Run.
      • UseWhen
        • Come Use ma viene eseguito solo se la condizione indicata é vera

      • Map & MapWhen
        • Serve per ramificare la pipeline.
        • Accetta un ‘path’ e un delegate in modo da configurare una middleware separata per il dato ‘path’.
        • //configure()
          public void Configure(IApplicationBuilder app, IHostingEnvironment env)
          {
          //Quando ricevo una richiesta con url /Home eseguo il metodo Execute
          app.Map("/Home", Execute);

          //?Search=Test
          app.MapWhen(context => {
          return context.Request.Query.ContainsKey("Search")
          }, ExecuteQeuryString

          public void Execute(IApplicationBuilder app)
          {
          app.Run(async context =>
          {
          await context.Response.WriteAsync("Execute");
          );
          }

          public void ExecuteQeuryString(IApplicationBuilder app)
          {
          app.Run(async context =>
          {
          await context.Response.WriteAsync("Execute QueryString");
          );
          }
          }
  • Routing =>
    • Semplifica l’URL per accede alle pagine/servizi della nostra applicaizone MVC
    • Path =>
      • RoutePath
        • E’ la parte fissa dell’URL che si vuole mappare 
        • /Customer/New/{id}
          /Customer/New/ => parte fissa (literal text)
      • RouteParametes
        • E’ la parte variabile dell’URL che si vuole mappare.
        • E’ una particolare sintassi che permette di definire i route templates.
        • /Customer/New/{id}
          {id} => parte variabile

          endpoints.Map("file/{name}.{extension}", async context => {
          await context.Response.WriteAsync($"File received {context.Request.RouteValues["name"]}");
          });

          1 - Parametro valore di default
          {id=1} => 1 é il valore di default se nell'URL non viene specificato tale parametro
          ma nell'URL é presente solo la parte fissa

          2 - Parametro opzionale
          //controllare con if(Context.Request.RouteValues.ContainsKey("id"))
          {id?}

          3 - Parameter constraint
          //Obbligo che il parametro id sia un intero
          //Se l'URL non meccia con il constraint allora
          //l'endpoint relativo a tale regola del routing non puo' essere applicato
          //Si cercherà di applicare le altre regole del routing per trovare il giusto endpoint
          {id:int}
          {id:int=4}
          {price:decimal}
          {numberofuser:long}
          {id:guid}
          {isActive:bool}
          {activationDate:datetime}
          {username:minlength(12))
          {phonenumber:maxlength(13))
          {code:length(4;8))
          {studentAge:range(6;18))
          {name:alpha)
          {age:regexp(^[0-9]{2}$)}
          {phone:regex(^\\d{{10}}$)}
      • Custom constraints class
    • Ordine di esecuzione se il routing trova più di un endpoint =>
      • Se l’URL meccia piu’ di una regola allora la decisione di quale applicare seguirà le seguenti regole
        1. URL Template con più segmenti (es a/b/c > a/b).
        2. URL Template con ‘literal text’ ha priorità sui ‘parametri’  (a/b > a/{b}).
        3. URL Template con parametri con constraints ha priorità su template con parametri ma senza constraints (es a/{i:int} > a/{i}).
        4. Call all parameter (es: a/**) ha la più bassa priorità.
    • Come parametrare il routing
      • Metodo 1 – Conventional base routing => 
        • using MyRouting;
          public void Configure(IApplicationBuilder app, IHostingEnvironment env)
          {
          app.UseStaticFiles(); //per visualizzare pagine HTML, CSS...
          app.UseMvc(); //per usare conntrollers
          app.UseMvcWithDefaultRoute(); //Applico il routing di default
          app.UseMvc(routes => //Applico il routing customizzato
          {
          ApplicationRoutes.LoadRoutes(routes);
          });
          }

          using Microsoft.AspNetCore.Routing;
          namespace MyRouting
          {
          public static class ApplicationRouting
          {
          public static void LoadRoutes(IRouteBuilder builder)
          {
          builder.MapRoute("route1", "/Customer/New/{id:int}", new
          {
          controller = "Customer",
          action = "Add"
          });

          builder.MapRoute("route2", "/Customer/New/{id:min(18)}", new
          {
          controller = "Customer",
          action = "AddAdult"
          });

          ....
          }
          }
          }
      • Metodo 2 – Attribute base routing (>= Asp.net MVC 5) =>
        • E’ possibile mappare il routing direttamente nel controller aggiungendo l’attributo Route[<action_map_url>] al metodo che definisce l’ <action> che si vuole mappare
        • Indicando il ‘route template‘ che imposta il mapping tra l’URL della HttpRequest e il metodo.
        • //RouteConfig.cs
          public static void RegisterRoutes(RouteCollection routes)
          {
          //E' necessario attivare l'Attribute routing esplicitamente
          //nel file di config del routing
          route.MapMvcAttributeRoutes();
          }

          //<ControllerName>.cs
          //E' possibile imporre ai parametri delle constraint tramite
          // - regex, range, min, max, minlength, maxlength, int, float, guid ..
          //Nell'esempio il primo parametro {year} è opzionale
          //L'ultimo parametro {day} ha il valore di default = 1
          [Route("<controllerName>/
          <actionName>/
          {year?}/
          {month:regex(\\d{2}):range(1,12)}/
          {day=1}]
          public ActionResult ActionName(year, int month)
          {
          ...
          }
    • Core
      • Il routing è un processo di matching delle richieste HTTP in entrata in base al metodo e l’URL HTTP e successiva invocazione degli endpoint corrispondenti. (middleware).

 

      • program.cs
        • //esempio di program.cs
          var builder = WebApplication.CreateBuilder(args);
          var app = builder.Build();

          //Attiva il routing
          //Seleziona gli endpoint ma non li esegue
          //Viene cioé selezionato un appropriato endpoint in funzione de:
          // - url path
          // - http method
          app.UseRouting();

          //Esegue gli endpoint cosi' come selezionati dal app.UseRouting();
          //Alle funzioni Map, MapGet, ecc.. devo fornire un middleware delegate (endpoint).
          //Puo' essere un'espressione lambda.
          app.UseEndPoints(endpoints =>
          {
          endpoints.Map("map1", async (context) => {
          await context.Response.WriteAsync("In map1");
          }),
          endpoints.MapGet(...),
          endpoints.MapPost(...),
          endpoints.MapControllers(...)
          });

          oppure

          build.services.AddControllesWithViews();
          app.UseRouting();
          app.MapControllers();
      • UseRouting()
        • UseRouting aggiunge il ‘route matching’ alla ‘middleware pipeline’.
        • Eseguito durante la pipeline request, questo middleware esamina il set di endpoint definiti nell’app (via app.UseEndPoints) e seleziona la corrispondenza migliore per la richiesta.
        • Solo dopo l’esecuzione di questo componente (della request pipeline di asp.net core) si potrà avere accesso all’appropriato endpoint tramite app.GetEndPoint()
      • UseEndPoints()
        • Definisce degli ‘endpoint alla ‘middleware pipeline’.
        • Ad ogni ‘endpoint’ é associato un ‘delegate’.
      • GetEndPoint()
        • Permette di investigare qual’é il delegate/endpoint attualmente associato all’url della http request attuale (solo dopo aver eseguto app.UseRouting() 
          • per logging purpose.
          • aggiungere dei valori alla querystring in particolari casi.
        • Proprietà:
          • DisplayName (spesso é l’url stesso)
          • RequestDelegate (é il delegate/endpoint che viene eseguito a seguito dell’url ricevuto)
        • app.Use(async (context, next) => 
          {
          Endpoint? endpoint = context.GetEndpoint();
          if(endpoint != null)
          ....
          await next(context);
          });
      • Map(delegateHandler)
        • Viene eseguito indipendentemente dal http verb (get,post…)
      • UseStaticFile() (enabling static content)
        • Questa dichiarazione aggiunge al middleware la gestione delle richieste di contenuti statici (es: immagini, pdf)
        • La cartella di default dove questo componente del middleware andrà a cercare i contenuti statici richiesti é “wwwroot”

        • var builder = WebApplication.CreateBuilder(args);
          var app = builder.Build();
          app.UseStaticFiles();
          app.UseRouting();
          app.UseEndpoints(endpoints =>
          {
          endpoints.Map("/", async (context) =>
          {
          await context.Response.WriteAsync("Hello");
          });

          });
          app.Run();
        • wwwroot customizzata (es: my-wwwroot): 
          • //nel caso si voglia usare una cartella customizzata per i file statici
            //sostituire la prima riga dell'esempio sopra con le seguenti 2 azioni:

            1 - sostituire la linea sopra con queste in basso:
            var builder = WebApplication.CreateBuilder(new WebApplicationOptions() {
            WebRootPath = "my-wwwroot"
            });

            2- aggiungere al file di progetto le seguenti voci:
            //.csproj
            <ItemGroup>
            <Content Include="my-wwwroot\**" />
            <Content Remove="wwwroot\**" />
            </ItemGroup>
  • Publish =>
    • Deployment-mode
      • Framework-dependent => 
        • Caso classico, nella directory scelta per il publish verrano deploiati più o meno gli stessi file che troviamo nella cartella debug del nostro progetto
        • Nel computer target deve essere installata la versione del .NET framework compatibile con l’applicazione deploiata
      • Self-contained =>
        • Nella cartella scelta per il publish verranno deploiati tutti i file (dipendenze) necessarie per eseguire la mia applicazione .NET senza che nel computer target sia installato  alcunché
        • E’ necessario indicare per quale piattaforma vogliamo deploiare in formato self-contained la nostra applicazione 

    • Single file =>
      • E’ possibile indicare al processo di publishing di creare un unico file deploiabile
      • Viene prodotto un unico zip file che viene direttamente estratto in memory e poi eseguito
      • Ceccare l’opzione ‘Produce single file’ 

    • Trimmer =>
      • E’ possibile indicare al processo di publishing di escludere dal deploi tutte le referenze che la nostra applicazione non usa
      • Ceccare l’opzione ‘Trim unused assemblies’ (vedi immagine sopra)

 

Environment e Configuration

  • Gestione degli environment =>
    • launchSettings.json
      • launchSettings.json

        "profiles": {
        "MyProject.Kestrel": {
        "commandName": "Project",
        "launchBrowser": true,
        "environmentVariables": {
        //applicazioni web asp.net core
        "ASPNETCORE_ENVIRONMENT": "Development | Staging | Production | <custom-environment> "

        oppure

        [//per tutti i tipi di applicazioni .Net Core non web
        "DOTNET_ENVIRONMENT" : ...]
        },
        "dotnetRunMessages": true,
        "applicationUrl": "http://localhost:5240"
        }
        }

        //program.cs
        app.EnvironmentName
        app.IsProduction()
        app.IsStaging()
        app.IsDevelopment()
        app.IsEnvironment("<custom-environment>")
    • Controller 
      • //HomeController.cs

        //Iniettare nel controller la classe WebHostEnvironment

        public class HomeController: Controller
        {
        private readonly IWebHostEnvironment _webhostEnvironment;

        //constructor
        public HomeController(IWebHostEnvironment webhostEnvironment)
        {
        _webhostEnvironment = webhostEnvironment;
        }

        //action
        [Route("/")]
        public IActionResult Index()
        {
        _webhostEnvironment.EnvironmentName
        _webhostEnvironment.IsDevelopment()
        _webhostEnvironment.IsStaging()
        _webhostEnvironment.IsProduction()
        _webhostEnvironment.IsEnvironment("<custom-environment>")
        }

        }
    • View – TagHelpers
      • //myview.cshtml
        <environment include="Production">
        "some html content here only for production"
        </environment>

        <environment exclude="Production">
        "some html content here only for development"
        </environment>
    • Process-level environment
      • In staging e in produzione non avendo a disposizione il file launchSettings.json é necessario specificare la variabile environment quando si lancia l’applicazione
      • //avendo disabilitato il file launchSettings.json tramite l'opzione --no-launch-profile
        //la variabile d'ambiente sarà = production
        dotnet run --no-launch-profile //ASPNETCORE_ENVIRONMENT=Production

        //E' possibile anche indicare una variabile d'ambiente specifica
        //settandola tramite la powershell di windows
        >$Env:ASPNETCORE_ENVIRONMENT="Staging"
        //o la shell normale
        >set ASPNETCORE_ENVIRONMENT="Staging"
        dotnet run --no-launch-profile //ASPNETCORE_ENVIRONMENT=Staging
  • Configurazione 
    • File appsettings.json

      • Leggere la configurazione
        • //appsettings.json
          {
          ...,
          "MyKey": "MyValue"
          }

          //program.cs
          var app = builder.Build();
          string myconfig = app.Configuration["MyKey"]; //MyValue
          oppure
          string myconfig = app.Configuration.GetValue<string>("MyKey", [default-value]); //MyValue
    • Altre sorgenti per la configurazione
      • Environment variables
      • Altri file di configurazione (Json, Ini, XML)
      • In-memory configuration
      • Secret manager

Databinding Framework

  • Angular
    • Typescript.
  • React
    • ES6.
    • Usa object-oriented JavaScript.
    • Necessità di extra moduli.
  • Vue
    • ES6.
    • Adetto a applicazioni meno complesse.
    • Più facile da apprendere-

 

.NET Watch

  • Permette di vedere anche lato server le modifiche fatte senza dovere fare il rebuild del progetto Impostando un listener che, ad ogni modifica del codice C#, ricompila il proggetto al volo
  • passo 1
    -- <project-name>.csproj  
    <ItemGroup> 
       <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="2.0.2" /> 
       <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.2" /> 
       <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.1" /> 
       <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" /> 
    </ItemGroup> 
    
    passo 2
    //dopo aver inserito la referenza al .NET Watch nel file di progetto, eseguire l'applicazione con il seguente comando.
    dotnet watch (invece di dotnet run)

 

Development Tips

  • Popolare 2 dropdown list correlate
    • //Abbiamo 2 input forma di tipo drop-down-list
      //Una volta selezionata la marca, devono apparire tutti i modelli legati alla marca
      Marca:   <selezione la marca>
      Modello  <seleziona il modello>
      
      Soluzione 1 (da preferire se la seconda drop-down-list contiene una grossa quantità di dati)
      -- progettare 2 end-point 
      - per avere la liste delle marche
      GET /api/marche => [ {id=1, name="marca1"}, {id=2, name="marca2"}, .. ]
      - per avere la lista dei modelli a partire dall' id_marca selezionato
       GET /api/marche/<marca-id>/modelli  => [ {id=1 name="modello1"}, {id=1 name="modello2"}, ..]
      
      Soluzione 2 (da preferire se si gestisce un piccolo set di dati) 
      -- progettare 1 solo end-point che dia la lista delle marche e per ogni marca la lista dei modelli associati
      GET /api/marche => [ {id=1, name="marca1", modelli = [{id=1, name="model1"},  {id=1, name="model2"}, .. ]},
                           {id=1, name="marca1", modelli = [{id=1, name="model1"}, {id=1, name="model2"}, .. ]},
                           .. ]
  • Disabilitare il meccanismo l’analisi null-state attiva di default in .NET 6
    • Disabilitare il controllo sui valori nulli in modo centralizzato
      -- <my-project>.csproj
      <PropertyGroup>
        <Nullable>Enable | Disable </Nullable>
      </PropertyGroup>
      
      Disabilitare il controllo sui valori nulli pagina per pagina
      #nullable disable

Risorse

  • REST Client for Visual Studio Code
    • Aprire Visual Studio Code e cercare ‘Rest Client’
    • Dopo l’installazione, creare un nuovo file formato .http
      • testapi.http
        //Impostare la chiamata desiderata
        GET https://localhost:7063/api/Events
        Content-Type: application/json
  • Swagger
  • Bootswatch.com  =>
    • E’ possibile trovare una serie di Bootstrap Template per cambiare quello di default (\Content\bootstrap.css).
    • Aprire il sito => cliccare su Themes => Selezionare tema => Download  il file <bootstrap.css>.
    • Tornare nell’app MVC e aggiungerlo alla cartella \Content .
    • In ultimo, modificare la configurazione in \App_start\BundleConfig.cs .
  • https://getbootstrap.com/docs/3.4/css/#forms
  • Bootbox.js =>
    • Libreria js che usa bootstrap per creare dialog box
    • //Nella console manager di Visual Studio
      Install-package bootbox
  • Jquery datatables
    • //Step 0: Nella console manager di Visual Studio
      Install-package bootbox

      //Step 1: Aggiungere al progetto i seguenti 2 js
      bundles.Add(new ScriptBundle("~/bundles/lib").Include(
      "~/Scripts/datatables/jquery.datatables.js",
      "~/Scripts/datatables/datatables.bootstrap.js"));

      //Step 2: Aggiungere al progetto il seguente file css
      bundles.Add(new StyleBundle("~/Content/css").Include(
      "~/Content/DataTables/css/dataTables.bootstrap.css"));

      //Step 3: Aggiungere nello script della pagina dove si vuole
      //usare il datatable di jquery il seguente comando
      <script>
      $(document).ready(function () {
      $("#customers").DataTable({
      ...
      ...
      }
      </script>
  • Auto Completion Tool =>
    • //configurare il plugin
      //passo1
      Install-package Twitter.Typeahead
      //passo2 \App_start\BundleConfig.cs
      Aggiungere il bundle al file "~/Scripts/typeahead.bundle.js"
      //passo3
      Aprire il sito https://twitter.github.io/typeahead.js/css/examples.css e prendere il
      contenuto e creare il file typeahead.css
      //passo 4
      Aggiungere il file typeahead.css al bundle

      //usare il plugin
      Andare alla pagina https://twitter.github.io/typeahead.js/examples/#remote
      e recuperare il seguente codice da customizzare
      <div id="customers">
      <input class="typeahead" type="text" placeholder="Oscar winners for Best Picture">
      </div>
      <script>    $(document).ready(function(){
      var vm = {};       var customersList = new Bloodhound({          datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),             queryTokenizer: Bloodhound.tokenizers.whitespace,             remote: {              url: '/api/customers?query=%QUERY',                wildcard: '%QUERY'             }         });         $('#customer').typeahead(          {             minLength:3 //l'utente deve digitare almeno 3 lettere
      highlight:true //le prime 3 lettere digitare saranno bold            },            {             name: 'customers',               display: 'name',               source: customersList          }).on("typeahead:select", function(e, customer) {
      wm.customerId = customer.Id;
      }
      );     });   </script>
  • Visual Studio Productivity Tools =>
    • Tools > Extensions & updates > Online => cercare ‘Visual Studio Productivity Power Tools’.
    • Tools > Extensions & updates > Online => cercare  ‘Web essential plugin’.
  • Toastr => visualizza messaggi  (success/error) 
    • install-package toastr

      //aggiungere nel file bundle
      "~/Content/toastr.css" 
      ~/Scripts/toastr.js                   
  • Elmah (Error loggin Modules and Handles) => Logga eccezioni non gestite (unhandled)
    • install-package Elmah 
    • Elmah crea un nuovo end point (accessibile solo in locale per ragioni di sicurezza) nel nostro sito per gestire il log degli errori non gestiti
      • http://mio_sito/elmah.axd
    • E’ possibile tramite le seguenti configurazioni attivare questo end point anche in remoto
      • \web.cofing
        //Localizzare all'interno del nodo <location> il nodo <authorization>
        //Gestire le autorizzazioni per poter visualizzare tale end point
        //anche in remoto
        <location path="elmah.axd" inheritInChildApplications="false">
        <authorization>
        <allow users="admin@my_site" />
        <deny users="*" />
        </authorization>
        </location>
    • E’ possibile memorizzare i log in un DB fisico (vedere la config da fare nella documentazione ufficiale di Elmah).
  • Paletta di colori
    • color.adobe.com
  • Glimpse tool (Real Time Diagnostic Tool) =>
    • Installare glimpse nel progetto:
      • install-package glimpse.mvc5
        install-package glimpse.ef6
    • End point =>
      • Glipse crea un nuovo end point nel nostro sito per i attivare/disattivare 
        • http://mio_sito/glimpse.axd
      • Questo end point è accessibile solo in localhost per ragioni di sicurezza
    • HttpHandler =>
      • Glimpse aggiunge in web.config un nuovo HttpHandler AXD file
      • Serve a iniettare dati nelle pagine client via cookies.
      • Questi cookies sono scambiati ad ogni richiesta tra client e server.
      • Il server può leggerli per diagnostica.
    • Diagnostica => Alle pagine nel nostra applicazione sono aggiunti 3 tab con parecchie informazioni aggiuntive
      • Http tab
      • Host tab
      • Ajax tab
      • G Icon panel