martedì 30 settembre 2014

Any Way the Wind Blows

Con il tasso di turnover per gli sviluppatori di applicazioni di punta di Ventozoom, qualche parte del sistema deve essere maledetto. Dopo che Robert è entrato nel team, e vedere come il suo predecessore gestito dizionari, lui può capire.

Di Mark Bowytz2014/09/29

Poiché atterraggio a Ventozoom sei mesi indietro, Robert ha lavorato su una singola applicazione - un'applicazione NET piuttosto complesso, con una base di codice criptico da abbinare. Dice di essere responsabile di diversi separazioni dei dipendenti, è il tipo di applicazione che ha iniziato la vita come una soluzione mod-sporco e veloce innocente per un piccolo gruppo di clienti che, con l'aiuto di un team di aggressivi ragazzi di vendita, rapidamente cresciuti.

E con il numero di clienti, così è cresciuto richieste di nuove funzionalità.

Aggiungere alla miscela risultante dei requisiti mutevoli e funzionalità di scorrimento, i soliti ingredienti di cui avete bisogno per trasformare un problema in una davvero grande casino strisciato via: mancanza di manodopera, la mancanza di competenza nel manodopera disponibile e, ultimo ma non meno importante, la mancanza di tempo. In altre parole, nel corso dei primi anni il prodotto è stato sviluppato da solo una manciata di persone fuori dalle loro rispettive profondità tecnici, con troppo lavoro da fare e non abbastanza tempo per farlo, o abbastanza sonno per quella materia.

Ma l'applicazione rende i mucchi aziendali di soldi, quindi va bene ... giusto?

Urrà per codici hash!
Mentre confronto di base all'attuazione hashing e uguaglianza nella classe Object non consente contenitori come il NET HashSet <> per lavorare con qualsiasi tipo senza alcuna preparazione particolare per la classe di valore, è anche la fonte di una miriade di (a volte sottili) bug del software: Se GetHashCode viene ignorato, una coerente attuazione di Equals solito deve anche essere fornita (e viceversa), e se l'uguaglianza dipende lo stato dell'oggetto (che è, il più delle volte), l'oggetto deve essere immutabile (perché il codice hash deve essere).

Naturalmente, non ogni programmatore conosce le sottigliezze dei gemelli speciali GetHashCode e Equals. E perché dovrebbero? Uno non deve gravare il proprio sé con tutta questa complessità per godersi semplicemente il fatto che qualsiasi tipo può essere messo all'interno di un HashSet, o utilizzato come tipo di chiave in un dizionario - per quella materia - proprio come il predecessore di Robert a quanto pare ha fatto.

Pubblicità

Quando il refactoring del codice vecchio (o, piuttosto, delicatamente lobotomizing con una motosega virtuale), ha trovato questo:

Dictionary <Dictionary <string, string>, Dictionary <string, stringa >> projectAttributes = new Dictionary <Dictionary <string, string>, Dictionary <string, string >> ();
...
Dictionary <string, string> newAttributes = ProjectUpdate.GetMappingAttributes (projectObject);
Dictionary <string, string> savedAttributes = ProjectUpdate.SaveMappingAttributes (projectObject);
projectAttributes.Add (newAttributes, savedAttributes);
...
ritorno projectAttributes;
Robert era incuriosito. A Dictionary (la versione NET di un array associativo) di dizionario per vocabolario? O forse ... un dizionario Meta . Aveva, naturalmente, già visto un po 'di codice "interessanti" nel corso della sua carriera, ma questo era nuovo e lui non riusciva a capire quale sia lo scopo di tale costrutto potrebbe essere. Cioè, fino a quando Robert ha trovato il luogo in cui è stato utilizzato.

! Coppie chiave-valore FTW
è scoperto, il predecessore di Robert stavano cercando un tipo di dati tupla, e nel buon ol 'giorni della Microsoft.NET Framework 3.5, non c'era nessuno - almeno non built-in. Così, naturalmente, la possibilità di fare le proprie cose, tuttavia, che possa essere, era spalancata:

Dictionary <Dictionary <string, string>, Dictionary <string, string >> projectAttributes =
  Arguments.Attributes come Dictionary <Dictionary <string, string>, Dictionary <string, string >>;
...
foreach (KeyValuePair <Dictionary <string, string>, Dictionary <string, string >>
  projAttributes in projectAttributes)
{
  newProjAttributes = projAttributes.Key come Dictionary <string, string>;
  savedProjAttributes = projAttributes.Value come Dictionary <string, string>;
}
Robert pensò la logica attraverso per un minuto - che cosa è un 2-tuple diverso da una coppia chiave-valore? E che cosa è un array associativo diverso da una lista di coppie chiave-valore? E che cosa è un singolo valore diverso da una lista con una sola voce? (E che cosa è un ciclo foreach oltre che una voce diversa da chiamare prima?)

Guardando la cosa da questo punto di vista, quasi diventa la cosa più naturale di utilizzare un dizionario con precisione una voce come una 2-tuple. Quasi.

Ancora una volta, tutta questa gioia è possibile solo perché ogni tipo NET implementa GetHashCode. A volte, Robert ancora si chiede se i creatori del NET Framework veramente pensato a quello che le cose che avrebbero aiutato a scatenare sul mondo del codice sorgente.

giovedì 18 settembre 2014

Tabelle Dividere in Entity Framework 6 per migliorare le prestazioni

ICi sono alcune occasioni quando si utilizza Entity Framework può davvero farti del male: Quando si dispone di tabelle con centinaia di colonne o tabelle con grandi carichi utili. Ecco come ottenere EF6 di fare la cosa giusta.

By Peter Vogel2014/09/17

La prima regola di accelerare l'applicazione è "Gestione accesso al database." Purtroppo, utilizzando Entity Framework (EF) significa rinunciare a qualche controllo intorno gestire l'accesso - in particolare, nel generare SQL. Come lettore ha sottolineato in un recente articolo , prendendo tutte le impostazioni predefinite di EF può risultare in un'applicazione che viene eseguita molto lentamente, anzi.

La questione sollevata è il lettore che, per impostazione predefinita, Entity Framework genera le istruzioni SQL per recuperare tutte le colonne che corrispondono alle proprietà sulle vostre classi di entità. Spesso, che saranno più dati del necessario. Ci stanno per essere momenti in cui, per esempio, invece si è dati da prima e l'ultima colonna di un cliente, ma l'entità che si sta utilizzando ha molte più proprietà che solo quei due colonne. Entity Framework, non essendo in grado di leggere la tua mente, recupererà le colonne della tabella che corrispondono a ogni proprietà sulla vostra entità, non solo i nomi e cognomi.

Mentre, sulla faccia di esso, che sembra inefficiente, il risultato di solito cade in un "nessun danno, nessun fallo" zona: Probabilmente non può misurare la differenza tra recupero solo le due colonne che si desidera e recuperare tutte le colonne.

Quando si potrebbe avere un problema
, ma per fare che "nessun danno, nessun fallo" reclamo, devo fare tre ipotesi. In primo luogo, sto supponendo che si sta costruendo un'applicazione online transazionale dove non recuperare molte righe alla volta (tipicamente, meno di una decina di righe) - cioè, "valore di transazione" una di righe. Poiché il numero di righe aumenta, alla fine la differenza tra ottenere due colonne e ottenere "tutte le colonne" recuperate diventerà abbastanza grande per fare la differenza. Tuttavia, la mia ipotesi sta per essere vero in quasi tutti i casi: Entity Framework è rivolto verso applicazioni online transazionali - non dovrebbe essere utilizzato per l'elaborazione in batch o applicazioni dove stai di elaborazione di centinaia o migliaia di righe alla volta di reporting .



Le mie altre due ipotesi sono ragionevoli, ma, a differenza mia prima ipotesi, non è sempre vero. Il secondo presupposto è che "tutte le colonne" è un numero piccolo - un paio di dozzine di colonne, per esempio. Se si dispone di una tabella con centinaia di colonne, allora è certamente possibile che si noterà la differenza tra il recupero di alcune colonne e il recupero "tutte le colonne," anche se si desidera recuperare solo un "valore di transazione" di righe.

La mia terza ipotesi è che la tabella non include alcun oggetto di grandi dimensioni (BLOB) colonne binarie (in Microsoft SQL Server, queste sono le colonne con i tipi varbinary, di immagine e di dati xml). Se si dispone di una colonna blob definito nella classe entità e stanno recuperando quella colonna quando non si desidera che allora si potrebbe facilmente avere un problema di prestazioni (anche se molto dipenderà anche da quanto è grande l'oggetto memorizzato nella colonna blob è) . E 'anche possibile che si può avere un problema se si dispone di grandi colonne di caratteri abbastanza oggetti (clob: testo, ntext, nvarchar (max), testo, varchar (max)).

In questi scenari, tuttavia, la correzione al vostro problema è sufficientemente semplice e probabilmente sufficientemente localizzate che non vorrei applicarla. Invece, mi piacerebbe costruire l'applicazione con un oggetto entità semplice che recupera tutte le colonne e vedere se il rendimento è accettabile. Solo se non stai ricevendo le prestazioni che si desidera si deve applicare la seguente correzione alle parti interessate della vostra applicazione.

Risolvere il problema
in modo efficace, si desidera implementare due modelli di recupero dei dati quando ottenere i dati dalle colonne che compongono le tabelle problema. Per la maggior parte delle proprietà sul vostro oggetto entità, si desidera che il valore predefinito Entity Framework: eager loading. Con eager loading, i dati della colonna vengono recuperati quando si elabora l'oggetto entità.

Per le colonne BLOB (o per le centinaia di colonne non è necessario) che si desidera un approccio diverso: lazy loading. Con lazy loading, i dati vengono recuperati dalla colonna quando si leggono le proprietà corrispondenti, non quando si tocca l'entità. Purtroppo, Entity Framework non dispone di un attributo LazyLoading che è possibile applicare alle proprietà di rinviare il recupero dei loro dati.

Tuttavia, Entity Framework supporta il caricamento lazy tra le entità di business che sono collegati attraverso una associazione. Sfruttando questa caratteristica è possibile ottenere il risultato desiderato, dividendo il vostro tavolo tra due o più entità unite da una associazione.

Con il vostro tavolo diviso in due (o più) entità collegate è possibile recuperare le proprietà desiderate ardentemente e pigramente rinviare recuperare le proprietà che non si desidera fino a quando ne avete bisogno. Avrete bisogno di almeno due soggetti: uno con le colonne che si desidera recuperare avidamente; e uno sull'altro lato di una associazione, con le proprietà Potrai recuperare pigramente. Per le "centinaia di colonne" problema, è necessario guardare la vostra applicazione per decidere quali colonne si utilizza insieme e gruppo di proprietà tuoi entità 'a sostegno di tale uso.

Per questo esempio, ho intenzione di prendere il più facile dei due problemi e guardare una tabella che ha una colonna blob. Ho modificato il database di Microsoft AdventureWorks quindi la tabella Customers contiene una colonna chiamata Picture immagine di tipo (presumibilmente questo vale una foto del cliente). In questa colonna, ho intenzione di implementare questa soluzione Codice First utilizzando EF6. (Tornerò a questo problema a fine mese di risolverlo utilizzando il progettista EF.)

Un codice prima soluzione
Nel codice prima soluzione scrivo le mie due classi di entità Cliente e segregare mia proprietà Picture (che voglio caricare pigramente) dalle proprietà che voglio caricare con entusiasmo. Entrambe le classi devono avere proprietà ID del cliente, che identifica un cliente. Le due classi hanno questo aspetto, inizialmente:

Public Class CustomerEager
  <Key>
  ID proprietà pubblica As Integer
  Public Property Cognome As String
  ... Resto di proprietà ...
End Class

Public Class CustomerLazy
  <Key>
  ID proprietà pubblica As Integer
  Proprietà pubblica Picture As Byte ()
End Class
Il passo successivo è quello di estendere la classe CustomerEager (la classe con le proprietà che voglio maggior parte del tempo), con una proprietà di navigazione che collega all'entità voglio caricare "pigramente". In questo esempio, ho chiamato i CustomerBLOBs proprietà e il tipo di dati restituisce è la mia classe CustomerLazy:

Public Class CustomerEager
  <Key>
  ID proprietà pubblica As Integer
  Public Property Cognome As String
  ... Resto di proprietà ...

  Public Overridable Property CustomerBLOBS Come CustomerLazy
End Class
Nel mio oggetto DbContext, ho intenzione di assumere io accedo solo le mie entità CustomerLazy attraverso la proprietà di navigazione sul mio entità CustomerEager. Come risultato, ho solo bisogno di impostare una proprietà per la mia collezione CustomersEager entità nel mio oggetto DbContext:

Public Class AdventureWorksLTEntitiesRevised
  DbContext Inherits

Public Property CustomersEager () Come DbSet (Of CustomerEager)
Il vero lavoro è nello stabilire il rapporto tra le due entità e la tabella che rappresentano. Lo faccio nel mio metodo DbContext OnModelCreating. In primo luogo, dico Entity Framework per generare CustomerLazy e CustomerEager oggetti entità della stessa tabella: Clienti. Questo codice è simile al seguente:

Protected Sub OnModelCreating override (ModelBuilder Come DbModelBuilder)
  MyBase.OnModelCreating (ModelBuilder)

  modelBuilder.Entity (Of CustomerLazy) (). ToTable ("Clienti")
  modelBuilder.Entity (Of CustomerEager) (). ToTable ("Clienti")
Ma, nello stesso metodo, ho anche bisogno di stabilire che l'associazione nell'entità CustomerEager fatta tramite la proprietà CustomerBLOBs ha due caratteristiche. In primo luogo, si tratta di una relazione uno-a-uno con l'oggetto all'altra estremità del rapporto. In secondo luogo, l'oggetto all'estremità dell'altro rapporto è sempre presente. Questo è ciò che fa questo codice:

  modelBuilder.Entity (Of CustomerEager).
    HasRequired (Function (c) c.CustomerBLOBs).
    WithRequiredPrincipal ()
End Sub
Caricamento di impazienza e Pigramente
Con i cambiamenti in atto, ora posso trattare tutti i clienti nel mio database con entusiasmo e recuperare l'immagine solo quando ne ho bisogno (pigramente). Codice come il seguente elaborerà tutti i clienti, solo occasionalmente recuperare l'entità con la proprietà blob:

Dim db As New AdventureWorksLTEntitiesRevised
Per ogni c Come CustomerEager In db.CustomersEager
  '... le proprietà di processo CustomerEager ...
  Se c.LastName = "Vogel" Allora
    '... Processo c.CustomerBLOBs.Photo
  End If
Prossimo
Tuttavia, questo codice si tradurrà in due viaggi al database: uno per recuperare l'entità CustomerEager e uno per recuperare i dati CustomerLazy. Questo ha senso quando è necessario solo l'entità pigro rado.

Tuttavia, quando si sa che avete bisogno di i dati provenienti da entrambe le entità, vuoi per evitare che il secondo viaggio al database. In tale scenario, si desidera recuperare il vostro soggetto pigro con entusiasmo (ovviamente, se avete bisogno di tutti i dati per tutto il tempo, non avresti dovuto dividere le tabelle). Entity Framework sarà avidamente caricare il soggetto pigro se si utilizza Entity Framework Includi metodo quando il recupero dell'oggetto ansioso. Includi cause Entity Framework per eseguire un carico desideroso dell'oggetto all'altra estremità della struttura di navigazione il cui nome si passa al metodo Includi. Questo esempio fa un carico desideroso dell'entità

indicato da un terreno CustomerBLOBs dell'entità ansioso:
Dim cust = (da C In db.CustomersEager.Include ("CustomerBLOBs")
            Dove c.LastName = "Vogel"
            Selezionare c) .Prima
... processo sia CustomerEager e CustomerLazy proprietà
Questi passaggi sono necessari solo se si è in uno dei due scenari che ho descritto all'inizio di questa colonna - e, anche allora, solo se effettivamente avete un problema di prestazioni. E se lo fai, le tabelle di divisione e con il supporto Entity Framework per eager loading e pigro attraverso le associazioni vi darà eager loading dei dati che si desidera e lazy loading dei dati che altrimenti potrebbero danneggiare la vostra performance. In altre parole: tutto quello che volete.

venerdì 12 settembre 2014

Risolvere la tirannia di HTTP 403 risposte per l'esplorazione delle directory in ASP.NET

L'utente non può saperlo, ma una risposta HTTP 403 quando si naviga ad una directory vuota è un serio rischio per la sicurezza.

Quello che il ?! Vuoi dire che se vado al mio sito che ha una cartella "scripts" dove ho messo tutta la mia JavaScript e io sono l'esplorazione delle directory disattivato (come giustamente dovrebbe) ed il server restituisce un "Forbidden" 403 (che giustamente dovrebbe), I 'sto mettendo le mie cose di internet a rischio di essere pwned ?!

Sì, perché rivela la presenza di una cartella denominata "script", che è una directory comune.

Beh, certo c'è una cartella sanguinosa denominata "script", tutta la mia sorgente HTML che è possibile vedere i riferimenti esso! Potrei chiamarlo "i-love-drunken-elefanti" e si poteva ancora vedere quindi qual è il punto ?!

Ma sarebbe comunque restituire un 403 che confermerebbe l'esistenza della risorsa e rappresentare un rischio directory enumerazione.

Ma si può scoprire la presenza di directory in ogni caso! Ok, in apps moderni come ASP.NET MVC potrebbero in realtà essere percorsi che non si traducono in attraverso percorsi fisici, ma ancora, questo è solo di essere pedante!

Il tuo sito non può andare dal vivo fino a risolvere il problema.

Uh, vorrei solo risolvere che per te ...

Alle prese con il problema di fondo
Questa è una di quelle cose che a torto oa ragione, che ho visto che spuntano da varie squadre di sicurezza e scanner automatici negli ultimi tempi. Si può obiettare che tutto quello che volete (e la gravità di essa è contestata), ma il fatto che alza la sua 'dibattito testa e le cause è sufficiente per risolvere proprio il dannato e da fare con esso. Oh - e per inciso, mi sono imbattuto un Netsparker sopra sono stato pwned? (HIBP) di recente e questo è stato uno dei risultati così yeah, mi colpisce troppo (anche se ho il lusso di scegliere di ignorarlo se mi piace!)

Netsparker segnalare una "risorsa Forbidden" su una cartella con la navigazione diretory disabilitato

Lascia che ti mostri il motivo per cui questo accade: la fonte di ogni pagina ho un tag <script> come questo:

< lo script src = "/ scripts / pwned? v = 2VUGdHR_7X4UImu2rpAgoquGkcvoIBUlzD35P5Y-dgo1"> </ script di >
Questo è in realtà utilizzando bundling e minification ASP.NET per combinare più script in uno e poi schiacciare tutto il codice JavaScript, ma cosa vuol dire è che sta implicando c'è un sentiero che è semplicemente "/ scripts". Se colpiamo percorso che otterremo il seguente:

Il / scripts percorso di ritorno 403

Sì, ho errori personalizzati configurati per l'applicazione, ma non prendo l'403,14 restituito quando l'utente non è autorizzato a sfogliare una directory senza pagina di default presente. Aspetta - qual è il bit .14? Questo è il codice di sub-stato che IIS restituisce per questo particolare sapore di un errore di "proibito". Non si vede il codice di stato secondario riflette esternamente nella risposta, ma esiste all'interno del processo di restituzione.

Troverete lo stesso errore viene restituito per una serie di altri percorsi che sono facilmente individuabili compreso / contenuto, / contenuti / immagini / fonts. Ogni volta che si dispone di una directory, si corre il rischio di un 403,14 da restituire e la gente di sicurezza sempre arrogante.

Un modo per affrontare questo è quello di creare un ingresso URL Rewrite regola tale che ogni richiesta di una cartella vuota conosciuta semplicemente viene espulso al tuo errore predefinito personalizzato o generico pagina 404. Che funziona, ma non è scalabile come si deve farlo per ogni singola cartella compreso quando si aggiungono più tardi su - il quale sarete inevitabilmente dimenticare.

No, qualcosa di più sostenibile si impone.

Facendo la risposta 403 (e perché non si può)
Il mio pensiero originale era questo: io creo una regola di riscrittura URL che cattura l'uscita 403 risposta e semplicemente riscrive a un 302 e aggiunge un'intestazione posizione o in altre parole, dice il browser per reindirizzare fuori alla mia solita pagina 404. Nonostante quanto brillante ho pensato che questa soluzione era, semplicemente non sarebbe giocare a palla. La risposta non veniva catturato dalla regola e il codice di stato non veniva riscritto. In preda alla disperazione, mi rivolsi a Stack Overflow e ha spiegato che io non posso cambiare IIS codice di risposta con URL Rewrite regola di uscita . Ho anche mandato collega MVP e URL Rewrite guru Scott Forsyth .

Potete leggere la risposta di Scott su Stack Overflow, ma insomma, che proprio non andava a lavorare. URL Rewrite non può riscrivere i codici di risposta in modo da qualcos'altro è stato chiamato per. Ci sono probabilmente modi di affrontare questo con un modulo HTTP o da qualche parte all'interno del ciclo di vita della risposta, ma non è una soluzione sola configurazione e ho davvero voluto mantenere questo vincolato a qualcosa che può essere fatto senza ricompilare, semplicemente perché questo è un importante vantaggio per un sacco di gente, soprattutto quando hanno già ottenuto siti web in esecuzione dal vivo e ottengono un risultato come questo.

Ma la soluzione ha cominciato a svilupparsi in risposta di Scott e tutto si riduce a come gli errori vengono gestiti all'interno di system.webServer.

Definizione di errori personalizzati in system.webServer (e come è solo una soluzione parziale)
Ecco la risposta di Stack Overflow che sulla superficie di esso, fa buon senso:

< httpErrors ErrorMode = " Personalizzato " >
  < errore statusCode = " 403 " subStatusCode = " 14 " percorso = " / Error / PageNotFound " responseMode = " ExecuteURL " />
</ httpErrors >
Vediamo ora come che guarda su HIBP:

Una pagina "Pagina non trovata" dopo aver configurato un errore HTTP in system.webServer

Ok, se realmente stiamo ottenendo da qualche parte - un pò. Stiamo vedendo "Pagina non trovata", e la richiesta è restituzione di un HTTP 200 che soddisferà il "Noi non vogliono vedere un 403" richieste, ma non è compatibile con un normale 404 sul sito. Per esempio:

Un vero e proprio 404 mostra un reindirizzamento 302

Si potrebbe sostenere che, mentre sì, non c'è più un 403 e che la casella di particolare può essere spuntata, il fatto che l'errore di esplorazione directory restituisce la "Pagina non trovata" pagina sul URL originale con un HTTP 200, mentre la genuina 404 reindirizza a / Errore / PageNotFound rivela effettivamente la stessa cosa di prima - che c'è una cartella fisica denominata "script".

Possiamo cominciare a risolvere il problema abbastanza facilmente modificando l'attributo "responseMode" a "Redirect" invece di "ExecuteUrl". Ecco cosa succede ora:

Reindirizzamento piuttosto che riscrivere l'errore system.webServer

Ah, così va meglio - specie di. Vedere come ora abbiamo la stessa risposta 302 seguito da un reindirizzamento a / Error / PageNotFound - questo è il bello. La cattiva bit è che abbiamo ancora una differenza fondamentale in quanto non c'è stringa di query "aspxerrorpath" quando il reindirizzamento avviene system.webServer. Non è possibile aggiungere neanche, non per la configurazione e né è possibile rimuoverlo dal errore personalizzato che gestisce la genuina 404, almeno non senza aggiustamenti stesso.

Tempo per ottenere creativo.

Rimozione della stringa di query aspxerrorpath
In realtà c'è una soluzione molto semplice per rimuovere la stringa di query dalla configurazione errori personalizzati ed è questo:

< customErrors mode = " On " defaultRedirect = " ~ / Error " >
  < errore statusCode = " 404 " redirect = " / Error / PageNotFound? foo = bar " />
</ customErrors >
Che poi significa che il percorso / foo da prima si dà questo:

Appening una stringa di query per non più alla pagina di errore CUSTM passa il valore aspxerror

Che potrebbe sembrare un po 'irregolare (ed è), ma ora si può andare e applicare la stessa stringa di query per il redirect in system.webServer e montone castrato è un vero e proprio 404 o si tratta di un realtà un 403,14, si otterrà lo stesso irregolare stringa di query - lavoro fatto! Non importa ciò che la stringa di query è chiamato ("foo" è puramente casuale) e in effetti non avrete nemmeno bisogno di una coppia nome valore, si può semplicemente aggiungere il punto interrogativo e che da sola è sufficiente.

Il problema, però, è che non mi piace stringhe di query screwy che appaiono nella risposta di no (apparente) buona ragione quindi abbiamo bisogno di un po 'di più a smanettare ancora ...

Riscrivere la posizione redirect
Il problema ora è che voglio prendere / Error / PageNotFound? Foo = bar e di sbarazzarsi della stringa di query. Nel caso in cui la semantica di redirect non sono del tutto familiari, quando un risposte del server web con un 302 come nella schermata afferrare sopra, è anche un colpo di testa "location" che assomiglia a questo:

Località: / Error / PageNotFound foo = bar?
Questo indica al browser di emettere una successiva richiesta di questo percorso, che è il motivo per poi vedere la seconda richiesta di cui sopra. Questo è ciò che dobbiamo cambiare e per fortuna si tratta di una soluzione semplice con URL Rewrite. Sembra proprio come questo:

< outboundRules >
  < regola nome = " Modifica intestazione posizione " patternSyntax = " ExactMatch " >
    < partita serverVariable = " RESPONSE_location " modello = " / Error / PageNotFound? foo = bar " />
    < azione di tipo = " Rewrite " value = " / Error / PageNotFound " />
  </ regola >
</ outboundRules >
Roba abbastanza semplice - prendere la "location" intestazione di risposta quando si sta cercando di reindirizzare il browser al nostro percorso di stringa di query irregolare e poi basta spogliarla fuori. Proviamo ora:

La stringa di query "? Foo = bar" ora rimosso dalla risposta

Ehi, questo sembra a posto, ora è esattamente lo stesso del comportamento quando si carica il percorso script! Bastava un errore personalizzato in system.webServer poi una stringa di query irregolare in errore system.web personalizzato per la risposta 404 e una regola di URL Rewrite per modificare il percorso. Come semplice è che ... ?!

Ok, devo essere un po 'faceto, perché dovrebbe essere più facile di quello, ma non è. Il lato positivo, però, ecco quello che abbiamo ora:

Non è più un 403 restituito durante la navigazione di una directory senza pagina di default quando l'esplorazione delle directory è disabilitata C'è.
La risposta da un vero 404 su un percorso inesistente è identica a navigare un percorso fisico senza doc predefinito.
E 'tutto fatto da solo la configurazione, nessun codice.
Quindi è perfetto allora? Beh un pò, tutto tranne che per una piccola cosa ...

Aggira reindirizzamenti di cortesia
Se sei aquila-eyed abbastanza, avrete notato una leggera differenza nel modo in cui ho chiesto foo e script in precedenza con il primo è denominato "/ foo" e quest'ultimo "/ scripts /". Quella barra finale fa una grande differenza, perché ecco cosa succede quando non esiste:

Manca slash causando un HTTP 301 poi un 302

Che follia è questa ?! A 301 "permanente" Redirect seguito da un "temporaneo" 302 redirect e infine la pagina "PageNotFound" - ciò che dà? E 'una cortesia reindirizzamento , cioè IIS è in realtà alla ricerca di un file di denominato "script" e quando non riesce a trovarlo, si rende conto che è c'è una directory con il nome e sputa indietro un 301 dicendo al browser molto esplicitamente che questo è davvero un cartella in virtù della barra finale appena aggiunto. Naturalmente dice anche il signor Hacker la stessa cosa, così mentre il 403 è andato ed i sentieri e le stringhe di query sono tutti buoni, che il reindirizzamento in più dà al gioco via.

Per essere onesti, come circa ora sarete senza dubbio ottenere un segno di spunta dalla gente di sicurezza in quanto non vi è più una 403 Se questo è il vostro obiettivo, allora si può fermare qui, infatti si potrebbe avere arrestato subito dopo il system.webServer voce di errore personalizzato, ma secondo me, che tipo di prova a metà mandato a quel paese. Se ho intenzione di fare questo, ho intenzione di farlo correttamente dannazione!

Sono andato avanti e indietro un po 'con Scott su questo fino a quando siamo venuti a un'implementazione che assomiglia a questo:

< system.webServer >
  < defaultDocument abilitato = " true " />
</ system.webServer >
Questo è abbastanza auto-esplicativo - disabilitare la funzionalità di documento predefinito. Ciò significa allora che il DefaultDocumentModule (che è di destra, senza spazi, è una cosa) non causa più la 301 per il percorso con la barra finale, al fine di implicare la richiesta è di una cartella che dovrebbe poi servire il documento predefinito. Significa che la richiesta appare come segue:

A 302 dal percorso script senza slash alla pagina di errore

Significa anche ciascuno dei seguenti scenari risponde in modo identico:

GET https://haveibeenpwned.com/PathDoesntExist
HTTP 302 -> / Error / PageNotFound

GET https://haveibeenpwned.com/PathDoesntExistWithTrailingSlash/
HTTP 302 -> / Error / PageNotFound

GET https://haveibeenpwned.com/scripts
HTTP 302 -> / Error / PageNotFound

GET https://haveibeenpwned.com/scripts/
HTTP 302 -> / Error / PageNotFound
Hallelujah - funziona!

Fatta eccezione per i tempi non è così. In realtà sempre funziona su HIBP, ma che è perché è un app MVC che implementa il routing per mappare un URL come https://haveibeenpwned.com/About al "Chi" controllore. Se fosse un app Web Forms e dipendeva da un file Default.aspx nella "About" directory allora avrei un problema del tutto nuovo.

Tuttavia, ci sono più strade per affrontare questo. Uno è quello di consentire solo i documenti predefiniti per una whitelist di percorsi. Ok, non molto scalabile ma certamente fattibile.

Un altro è quello di utilizzare URL Rewrite per reindirizzare le query ai percorsi specifici che si basano su un documento predefinito per l'URL che contiene esplicitamente il nome del file in esso, ad esempio "/foo/default.aspx". Ancora una volta, non reale scalabile e anche non reale bella.

Una soluzione migliore sarebbe quella di utilizzare la riscrittura URL o in altre parole, impostare la modalità di risposta system.webServer a "ExecuteURL" e gli errori personalizzati reindirizzano alla modalità "ResponseRewrite". Questo sarà quindi mostrare la pagina di errore sul URL richiesto senza alcun reindirizzamento di sorta. Se siete preoccupati per le implicazioni SEO di un HTTP 200 che mostra una pagina di errore, si può sempre cambiare il codice di risposta nella pagina di errore in sé.

Anche in questo caso, è più aggiustamenti, ma che l'ultima opzione è probabilmente la scelta migliore se hai una dipendenza sulla presenza di documenti predefiniti in cartelle.

Riassunto
Se io sono onesto, tutto questo è un sacco di gingillarsi per molto poco beneficio fine. Persone caritatevoli Meno di me lo chiamerei "teatro di sicurezza" e nello spettro di rischi potenzialmente sfruttabili, questo è il modo, in fondo in fondo.

Ma niente di tutto questo cambia il fatto che strumenti di sicurezza e squadre ritengono che questo sia un rischio e solleva una bandiera ed è necessario per risolvere il problema. Che in ultima analisi, può essere fissato molto facilmente (ma solo perché ora avete la strada tracciata per voi!) e tramite la configurazione solo è un buon motivo sufficiente per farlo e andare avanti.

Oh, e nel caso in cui si dispone di un mezzo più efficiente per farlo sia con configurazione o codice, lasciate un commento con alcuni consigli. Questo è il genere di cosa che la gente inevitabilmente Google la loro strada nel più facile e siamo in grado di rendere la vita su coloro che seguono, il migliore.

Giusto per rendere più reale facile, ecco tutte le config in un colpo solo:

< system.web >
  < customErrors >
    < errore statusCode = " 404 " redirect = " / Error / PageNotFound? foo = bar " />
  </ customErrors >
</ system.web >
< system.webServer >
  < riscrittura >
    < outboundRules >
      < regola nome = " Modifica intestazione posizione " patternSyntax = " ExactMatch " >
        < partita serverVariable = " RESPONSE_location " modello = " / Error / PageNotFound? foo = bar " />
        < azione di tipo = " Rewrite " value = " / Error / PageNotFound " />
      </ regola >
    </ outboundRules >
  </ riscrittura >
  < httpErrors ErrorMode = " Personalizzato " >
    < errore statusCode = " 403 " subStatusCode = " 14 " percorso = " / Error / PageNotFound " responseMode = " Redirect " />
  </ httpErrors >
  < defaultDocument abilitato = " true " />
</ system.webServer >
Non, non ti senti più sicuro adesso? :)

lunedì 8 settembre 2014

Di Visual F # Include Windows Phone 8.1 a sostegno Biblioteca Portable

La versione 3.1.2 di Microsoft F # derivazione ha ampliato il supporto biblioteca portatile e permette l'opzione Pubblica in progetti Web e Azure.

By Michael Domingo2014/09/03

Visual F # team di Microsoft blogged circa un rilascio minore di F # che "confeziona gli ultimi aggiornamenti per il compilatore F #, interattivo, tempo di esecuzione, e l'integrazione di Visual Studio." Di Visual F # 3.1.2 arriva poco dopo un out-of-band 3.1.1 rilascio nel mese di gennaio che ha aggiunto il supporto per Desktop e Web Express.

Di Visual F # è la versione Microsoft di F #, un linguaggio di programmazione fortemente tipizzato derivato da ML che è fortemente influenzato da C # e Python. F # si supporta un certo numero di stili di programmazione, tra cui object-oriented, funzionale, e linguaggi procedurali, ed è anche in grado di generare codice in JavaScript e per l'utilizzo con le GPU. Di Visual F # è un'estensione di quella lingua che può lavorare entro i confini della Microsoft NET Framework. (Una definizione approfondita della lingua può essere trovato sul MSDN Library .

La release 3.1.2 punto aggiunge una manciata di caratteristiche nuove e utili, tra cui il supporto biblioteca portatile esteso. Questa si presenta sotto forma di modelli di progetto F # che supportano il profilo 78 e profilo 259 profili portatili, entrambi i quali supportano Windows Phone 8.1. Il risultato è che le biblioteche portatili F # possono essere aggiunti a Windows Phone 8.1 progetti direttamente.

Un altro cambiamento significativo osservato nel blog ha a che fare con il flusso di lavoro. In particolare, questa versione di Visual F # supporta non-bloccaggio #r riferimenti mentre si lavora con Visual F # interattiva in uno sviluppo rapido-iterazione. Riferimenti di montaggio sono attivati ​​per impostazione predefinita e gli sviluppatori tendono a incontrare gli errori perché, quando uno sviluppatore aggiunge un riferimento all'assembly #r, si blocca che il montaggio su disco. Tutto ciò che serve per configurare come un riferimento a un assembly non-bloccaggio è quello di consentire con l'+ interruttore shadowcopyreferences.

Altri aggiornamenti includono la possibilità di pubblicare i progetti F # Web e Microsoft Azure direttamente da Visual Studio, e alcune modifiche linguistiche minori: migliorata la compatibilità Unix con la possibilità di utilizzare le direttive baracca in F # script (da non confondere con la compatibilità William Hung ), il supporto per affettare alto-dimensionale in codice compilato, e il supporto per gli spazi nei identificatori di casi modello attive.

martedì 2 settembre 2014

Creazione personalizzati HttpClient gestori

Accesso alle API Web può essere fatto facilmente utilizzando il gestore HTTPClient. Questa classe accelera il processo di sviluppo di accedere ai dati da un Web API, e consente di personalizzare i gestori cliente in caso di necessità.
HTTPClient è un oggetto utilizzato per l'accesso di richiesta e risposta dei messaggi da e verso API web. Come chiamate client-server tradizionali, la HTTPClient chiama il server utilizzando uno specifico URI, aspetta il risultato, quindi restituisce un oggetto risposta al chiamante.

Come per tutto il resto nello sviluppo di software, la necessità finirà per sorgere per lo sviluppo personalizzato. Per fortuna, la classe HttpClient consente per questo. Un gestore di messaggi singolo, o un numero di gestori, possono essere creati e aggiunti in un ordine specifico per lo stack di chiamate utilizzando il metodo HttpClientFactory.Create.

Uno o più gestori di messaggi personalizzati possono essere creati e organizzati in una catena di gestori che vengono automaticamente chiamato con ogni richiesta e la risposta. Una volta creata la classe del gestore messaggio, esso può essere utilizzato per eseguire operazioni personalizzate durante la sequenza di chiamata. Osservando lo schema di figura 1 , la sequenza inizia con la richiesta del cliente, poi continua attraverso la catena di gestori. Una volta che il messaggio raggiunge il server, la risposta viene inviata indietro propagazione attraverso la catena dei gestori in ordine inverso.


Figura 1. Flusso dei messaggi per la richiesta del cliente e risposta
Per dimostrare i gestori personalizzati sul cliente, sarà necessaria una API Web per inviare richieste e ricevere risposte da. In un precedente articolo , ho dimostrato come è possibile creare un Web API. Il codice utilizzato in tale articolo servirà come l'API Web per questo articolo, con una piccola modifica. Nel metodo Registro della App_Start \ WebApiConfig.cs di file, aggiungerò la dichiarazione config.Formatters.Add (nuovo JsonMediaTypeFormatter ()) ;. Ciò consentirà l'API Web per formattare i dati di output in formato JSON. Si può vedere il codice completo in Listato 1 .

Listato 1: Contenuto del WebApiConfig.cs (in API Web di progetto) dopo la modifica
Pubblicità


utilizzando System.Net.Http.Formatting;
utilizzando System.Web.Http;

namespace CarInventory21
{
  pubblico WebApiConfig classe statica
  {
    public void Registro static (HttpConfiguration config)
    {
      // Configurazione e servizi Web API

      // percorsi API Web
      config.MapHttpAttributeRoutes ();

      config.Routes.MapHttpRoute (
        Nome: "DefaultApi",
        routeTemplate: "api / {comando} / {id}",
        default: nuovo {id = RouteParameter.Optional}
      );
      
      config.Formatters.Clear (); // Rimuovere tutti gli altri formattatori
      config.Formatters.Add (nuovo BsonMediaTypeFormatter ()); // Abilita BSON nel servizio Web
      config.Formatters.Add (nuovo JsonMediaTypeFormatter ());
    }
  }
}
Ora l'API Web è pronto per essere utilizzato con il nuovo demo applicazione client. Per questo, creerò una nuova applicazione Windows Form, come si vede in figura 2 .

 
[Clicca sull'immagine per ingrandirla.] Figura 2. Creazione di un'applicazione Windows Form come un client API Web
L'interfaccia utente per l'applicazione è mantenuto semplice per scopi dimostrativi, come si vede in Figura 3 L'applicazione client demo è destinato a funzionare da C:. \ WebAPI21ClientDemo.

 
[Clicca sull'immagine per ingrandirla.] Figura 3. ui per Web Demo Client API
Dopo aver impostato l'interfaccia utente in figura 3 , io aggiungo la classe Car ai frmMain.cs. La classe Car è un modello dei dati ricevuti dalla chiamata API web. E 'lo stesso modello utilizzato nel progetto Web API. Assicurarsi di aggiungere questo dopo la frmMain classe nello spazio dei nomi CarInventory21Client del file frmMain.cs. In caso contrario, verrà visualizzato un errore quando si tenta di visualizzare il form designer. Ecco la classe Car:

public class Auto
{
  pubblica Int32 ID {get; set; }
  pubblica Int32 Anno {get; set; }
  stringa di rendere pubblico {get; set; }
  stringa Modello pubblico {get; set; }
  public string colore {get; set; }
}
Avanti, creerò un nuovo gestore classe personalizzata denominata CustomHandlerA, come si è visto in Listato 2 . Questa classe sarà anche nello spazio dei nomi CarInventory21Client del file frmMain.cs, ma al di sotto della classe frmMain. Vi spiegherò il motivo di questo più tardi.

Listing 2: La classe CustomHandlerA 
Classe CustomHandlerA: DelegatingHandler
{
  CustomHandlerA pubblico ()
  {
  }
     
  protected override async Task <HttpResponseMessage> SendAsync (HttpRequestMessage richiesta, 
    CancellationToken CancellationToken)
  {
    // Aggiungere funzionalità personalizzate qui, prima o dopo base.SendAsync ()
    WriteLog ("CustomHandlerA () - prima handler interno");
    HttpResponseMessage risposta = attendere base.SendAsync (richiesta, CancellationToken);
    WriteLog ("CustomHandlerA () - dopo handler interno; response.StatusCode =" + 
      response.StatusCode);

    ritorno di risposta;
  }

  WriteLog public void (string msg)
  {
    StreamWriter sw = new StreamWriter ("C: \\ WebAPI21ClientDemo \\ LogFile.txt", true);
    sw.WriteLine (DateTime.Now.ToString () + "" + Msg);
    sw.Close ();
  }
}
La funzionalità di questa classe è minimo e mantenuto semplice per scopi dimostrativi. Il gestore registra semplicemente un messaggio in un file di log prima e dopo la chiamata al metodo base.SendAsync. Questi messaggi vengono registrati in un file di testo utilizzando il metodo WriteLog.

Per dimostrare la sequenza di chiamate utilizzando più gestori personalizzati durante una chiamata API Web, sarò anche creare CustomHandlerB. Questa classe avrà la stessa funzionalità CustomHandlerA, ma verrà utilizzata per dimostrare la sequenza di chiamate da un gestore ad un altro.

Una volta completato, i frmMain.cs file conterrà quattro classi utilizzate in questo esempio:

frmMain: classe per Windows Forms funzionalità
Auto: modello di dati utilizzato per de-serializzare i dati ricevuti
CustomHandlerA: classe di handler HttpClient personalizzato
CustomHandlerB: classe per abitudine HttpClient gestore
Tutte le classi saranno nello spazio dei nomi CarInventory21Client. In genere, l'ordine degli oggetti all'interno di un file è irrilevante. Tuttavia, in un'applicazione Windows Form, l'errore in figura 4 viene rilevato se la classe Windows Form non è la prima classe della lista.

 
[Clicca sull'immagine per ingrandirla.] Figura 4. Errore Grazie al modulo di classe non essere primo nel file
L'errore si legge: "Il frmMain classe può essere progettato, ma non è la prima classe nel file di Visual Studio richiede che i progettisti utilizzano la prima classe nel file Spostare il codice della classe in modo che sia la prima classe nel file e provare.. caricare di nuovo il progettista. " Per risolvere questo errore, spostare semplicemente la classe del form alla parte superiore dello spazio dei nomi.

Guardando la classe CustomHandlerA più in dettaglio, prima che sarà ereditato da System.Net.Http.DelegatingHandler. La classe DelegatingHandler è un tipo di base per gestori HTTP, delegando l'elaborazione dei messaggi di risposta HTTP a un altro gestore, noto come il "gestore interiore." Per saperne di più su questa classe, vedere la pagina di MSDN Library, " DelegatingHandler classe . "

Una volta che la classe DelegatingHandler viene ereditata, il metodo SendAsync può essere ignorato. Questo sarà il metodo per eseguire codice personalizzato, prima o dopo la chiamata al gestore interno (base.SendAsync). Il base.SendAsync è il metodo che invia la richiesta al successivo gestore della catena.

Guardando il codice del gestore eventi per btnGet_Click nel Listato 3 , prima della firma del metodo è stato modificato per restituire in modo asincrono, utilizzando la parola chiave "async".

Listato 3: btnGetClick da frmMain.cs 
privato async vuoto btnGet_Click (object sender, EventArgs e)
{
  CustomHandlerA Cha = new CustomHandlerA ();
  CustomHandlerB CHB = new CustomHandlerB ();
  HttpClient client = HttpClientFactory.Create (CHA, CHB);

  client.BaseAddress = new Uri ("http: // localhost: 15324 /");
  client.DefaultRequestHeaders.Accept.Clear ();
  client.DefaultRequestHeaders.Accept.Add (
    nuovo MediaTypeWithQualityHeaderValue ("application / json"));
            
  HttpResponseMessage risposta = attendere client.GetAsync ("api / auto");
  se (response.IsSuccessStatusCode)
  {
    Auto [] = myCarArray attendono response.Content.ReadAsAsync <Auto []> ();
    foreach (myCar auto in myCarArray)
    {
      txtResults.AppendText (string.Format ("{0} \ t {1} \ t {2} \ n \ n", myCar.Id, myCar.Make, 
        myCar.Model));
    }                    
  }   
}
Successivamente, vengono istanziati istanze del CustomHandlerA e CustomHandlerB. Tali istanze vengono poi utilizzati durante la creazione di un oggetto HttpClient. Passando istanze dei gestori personalizzati per il metodo Create, Sono la creazione di un oggetto client con uno specifico ordine di delegare gestori. I prossimi tre istruzioni nel codice impostare URI e il tipo di formattazione JSON Media.

Dopo le proprietà richieste dell'oggetto client sono impostate, la chiamata client.GetAsync viene eseguito. Questo restituisce un oggetto HttpResponseMessage contenente i dati richiesti in formato JSON. I dati possono poi essere analizzati dall'oggetto response.Content. I risultati verranno visualizzati nella finestra dell'applicazione, come mostrato nella Figura 5 .

 
[Clicca sull'immagine per ingrandirla.] Figura 5. risultati visualizzati dopo aver chiamato il Web API
Guardando il contenuto del file di log (LogFile.txt), visto in qui, vedrete l'ordine di esecuzione:

2014/06/12 12:07:52 CustomHandlerA () - prima handler interno
2014/06/12 12:07:52 CustomHandlerB () - prima handler interno
2014/06/12 12:07:52 CustomHandlerB () - dopo handler interno; response.StatusCode = OK
2014/06/12 12:07:52 CustomHandlerA () - dopo handler interno; response.StatusCode = OK