Archivi categoria: Microsoft .Net

Esperimenti e studi vari sull’infinito mondo di .Net

Inno Setup & Browser Helper Objects & IE Toolbars

In un mio recente progetto, ho avuto la necessità di creare ,per Internet Explorer, un Browser Helper Object (un plugin che sfrutta le stesse interfacce e tecnologie delle Toolbar) che eseguisse delle operazioni custom su un particolare sito in ambiente Intranet.
La realizzazione del BHO, è relativamente semplice usando le interfacce IObjectWithSite e le direttive di interoperabilità del codice.
Per il setup invece è tutto particolarmente difficile. Le interfacce del BHO vanno registrate nel registry di Windows e rese accessibili al sistema di plugin di IE.
Per far questo ho usato un software free, INNO SETUP, che permette di generare degli installer a partire da uno script.
Inno Setup

Lo script, permette di definire il comportamento del setup, le informazioni da visualizzare nelle maschere, e tutte le operazioni da effettuare sul registry di Windows, compresa la problematica di differenze di posizionamento e nome di alcune voci tra i sistemi a 32 e a 64 bit. Il Bho di cui realizzo il setup è compilato in maniera da funzionare compatibilmente con tutti i sistemi e quindi richiede un setup compatibile sia con i sistemi a 32 che a 64 bit.

Lo script inizia con una serie di definizioni di costanti usate poi nel resto delle direttive, tra cui il nome della dll, il nome del namespace utilizzato, e il nome della classe interna che viene esposta al browser, contenente i metodi pubblici da utilizzare.

1
2
3
4
5
6
7
8
9
#define MyAppDescription "My Helper Objects"
#define MyAppName "MyBHO"
#define MyAppVersion "1.0.0.0"
#define MyAppPublisher "Metalide"
#define MyAppURL "https://www.metalide.com"
#define MyAppDllName "MyBHO.dll"
#define MyAppId "{A80C384E-4178-4AD0-85BC-3196241437C5}"
#define MyAppNamespace "MyBHO.MyHelperObject"
#define MyAppClass "MyBHOExtension"

Subito dopo si crea una sezione [Setup] per associare le costanti ai parametri dell’installer e altri attributi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Setup]
AppId={#MyAppName}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\MyTools\MyHelperObjects
DefaultGroupName=My_IETOOLS
DisableProgramGroupPage=yes
DisableDirPage=yes
CreateAppDir=yes
OutputBaseFilename=My_IETOOLSBHOSetup
Compression=lzma
SolidCompression=yes

Viene poi impostata la lingua del setup e il path di destinazione della dll (che in questo caso va registrata nella GAC di Windows), e l’eventuale icona da usare per le voci di unistall:

1
2
3
4
5
6
7
8
[Languages]
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"

[Files]
Source: "D:\Dev\MyBho\MyBHOBHOSETUP\MyBHO.dll"; DestDir: "{app}"; Flags: ignoreversion

[Icons]
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"

Fatto questo si passa alla sezione [Code] che mantiene delle funzioni (sono scritte in Pascal) generiche richiamate dai metodi “hook” gestiti da Inno stesso,come ad esempio il methodo InitializeSetup che viene eseguito a startup dell’installazione.
In questa sezione ho messo un metodo per la rilevazione dell’installazione su sistemi a 64 e 32 bit e sulla verifica del framework .net installato (il bho che ho realizzato in c# richiede, visto il codice managed, la presenza di .net 4.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[Code]
function IsDotNetDetected(version: string; service: cardinal): boolean;
//    'v1.1.4322'     .NET Framework 1.1
//    'v2.0.50727'    .NET Framework 2.0
//    'v3.0'          .NET Framework 3.0
//    'v3.5'          .NET Framework 3.5
//    'v4\Client'     .NET Framework 4.0 Client Profile
//    'v4\Full'       .NET Framework 4.0 Full Installation
//service: per la presenza richiesta di service pack:
//    0               No service packs
//    1, 2, etc.      Service pack 1, 2, etc..
var
    key: string;
    install, serviceCount: cardinal;
    success: boolean;
begin
    key := 'SOFTWARE\Microsoft\NET Framework Setup\NDP\' + version;
    // .NET 3.0 usa InstallSuccess nella chiave Setup
    if Pos('v3.0', version) = 1 then begin
        success := RegQueryDWordValue(HKLM, key + '\Setup', 'InstallSuccess', install);
    end else begin
        success := RegQueryDWordValue(HKLM, key, 'Install', install);
    end;
    // .NET 4.0 usa Servicing al posto di SP
    if Pos('v4', version) = 1 then begin
        success := success and RegQueryDWordValue(HKLM, key, 'Servicing', serviceCount);
    end else begin
        success := success and RegQueryDWordValue(HKLM, key, 'SP', serviceCount);
    end;
    result := success and (install = 1) and (serviceCount >= service);
end;



function Install32BitOn64Bit(S: String):string;
begin
    if IsWin64 then
      Result := 'Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{#MyAppId}'
    else
      Result := 'Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{#MyAppId}'
end;

function InitializeSetup(): Boolean;
begin
  if  IsDotNetDetected('v4\Client', 0) or  IsDotNetDetected('v4\Full', 0) then
      Result :=true
  else begin
      MsgBox('Attenzione Framework .NET 4.0 non rilevato', mbError, mb_Ok);
      Result :=false;
     end
 
end;

Come ultimo step del nostro script andiamo ad indicare nella sezione [Registry] tutte le chiavi da impostare (e sono molte) per il funzionamento del nostro componente come plugin di IE. Il guid che si passa, è il guid associato serve a indicare i componenti .net.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[Registry]
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; Flags: uninsdeletekey
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: ; ValueData: {#MyAppNamespace}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: MenuText; ValueData: {#MyAppName}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}"; ValueType: string; ValueName: HelpText; ValueData: {#MyAppDescription}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\Implemented Categories\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ; ValueData: mscoree.dll
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ThreadingModel; ValueData: Both
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Class; ValueData: {#MyAppNamespace}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: CodeBase; ValueData: file:///{pf}\MyTools\MyBrowserHelperObjects\{#MyAppDllName}
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Assembly; ValueData: {#MyAppName}, Version={#MyAppVersion}, Culture=neutral, PublicKeyToken=dedfcbc464c1d691
Root: HKCR; Subkey: "CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: RuntimeVersion; ValueData: v4.0.30319

Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; Flags: uninsdeletekey
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: ; ValueData: {#MyAppNamespace}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: MenuText; ValueData: {#MyAppName}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}"; ValueType: string; ValueName: HelpText; ValueData: {#MyAppDescription}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\Implemented Categories\{{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}"
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ; ValueData: mscoree.dll
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: ThreadingModel; ValueData: Both
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Class; ValueData: {#MyAppNamespace}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: CodeBase; ValueData: file:///{pf}\MyTools\MyBrowserHelperObjects\{#MyAppDllName}
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: Assembly; ValueData: {#MyAppName}, Version={#MyAppVersion}, Culture=neutral, PublicKeyToken=dedfcbc464c1d691
Root: HKLM; Subkey: "Software\Classes\CLSID\{{#MyAppId}\InprocServer32"; ValueType: string; ValueName: RuntimeVersion; ValueData: v4.0.30319

Root: HKLM; Subkey: {code:Install32BitOn64Bit};  Flags: uninsdeletekey
Root: HKLM; Subkey: {code:Install32BitOn64Bit}; ValueType: dword; ValueName: NoExplorer; ValueData: 1

Fatto questo, eseguendo lo script dal compilatore di Inno Setup, è possibile ottenere l’installer/uninstaller del nostro componente.

Ovviamente questo tutorial è il frutto di molte ricerche sulla rete, e di riferimenti presi da diversi blogger che hanno affrontato l’argomento, a cui vanno i miei ringraziamenti, e a quel pozzo infinito di spunti e riflessioni che è la comunità di StackOverflow, a cui va la mia gratitudine eterna 🙂 .

Usare RouteData con mvc4 per rounting a Runtime in global.asax

Spesso capita di dover eseguire dei routing dinamici all’interno delle applicazioni sviluppate con MVC3 e MVC4 della Microsoft, e molte volte non è possibile eseguire direttamente una redirect, come nel mio caso specifico, dove dovevo eseguire un redirect all’interno del Global.asax, durante la fase di Application_EndRequest,senza scatenare un loop infinito di redirect.
Ci vengono in aiuto le api dell’MVC ed in particolare la RouteData, che permette di passare tutti i dati di chiamata ad una particolare action di uno specifico Controller, instanziato programmaticamente:

1
2
3
4
5
6
7
var rd = new RouteData();
rd.DataTokens["area"] = null;
rd.Values["controller"] = "NomeController";
rd.Values["action"] = "NomeAction";

IController c = new NomeController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));

Estendere il WebClient per gestire un CookieStore

In C# , le system.net mettono a disposizione un oggetto chiamato WebClient per fare delle chiamate http in post a risorse identificate da un URI.

Nel mio caso l’ho utilizzato per fare delle chiamate dal CodeBehind di un applicazione web, per interrogare dei servizi web esposti in maniera restfull che accettavano dati in POST.

Dovendo gestire la sessione con questi servizi di backend e a volte avendo la necessità di dovergli pasare dei Cookie di autenticazione o altri cookie , mi sono trovato a doverlo estendere (per non lavorare direttamente con il WebRequest, oggetto su cui poggia il WebClient).

Il webclient offre pochissime funzionalità, ma estenderlo è molto facile.

In questo esempio aggiungiamo la possibilità di creare un proprio CookieStore, prima di una chiamata, in modo da potergli passare tutti i cookie (presi ad esempio dalla Request arrivata alla nostra pagina aspx) necessari al servizio che risponde all’URI interrogata.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
using System;

using System.Net;

using System.Web;

public class ExtendedWebClient : WebClient

{

    private CookieContainer m_container = new CookieContainer();

    protected override WebRequest GetWebRequest(Uri address)

    {

        WebRequest request = base.GetWebRequest(address);
        HttpWebRequest webRequest = request as HttpWebRequest;
        if (webRequest!=null){
            webRequest.CookieContainer = m_container;
        }
        return request;
    }

    public void SetCookies(HttpCookieCollection collection,string domain){
        m_container = new CookieStore();
        for (int j=0;j<collection.Count;j++){
            HttpCookie httpCookie = collection.Get(j);
            Cookie cookie = new Cookie();
            cookie.Domain = domain;
            cookie.Expires = httpCookie.Expires;  
            cookie.Name = httpCookie.Name;
            cookie.Path = httpCookie.Path;
            cookie.Secure = httpCookie.Secure;
            cookie.Value = httpCookie.Value;
            m_container.Add(cookie);
        }
    }
}

Oltre a gestire il cookieStore, dobbiamo preoccuparci della conversione dei cookies da HttpCookie (arrivati dalla Request) a quelli più dettagliati della System.Net.

Per utilizzare il nostro nuovo webclient e passargli i cookie possiamo fare in questo modo:

1
2
3
4
5
    ExtendendWebClient client = new ExtendedWebClient();
    client.SetCookies(request.Cookies,request.Url.Host);
    byte[] data = client.DownloadData("http://www.google.it");
    System.Text.Encoding enc = System.Text.Encoding.UTF8;
    string output = enc.GetString(data);

In questo modo tutti i cookie vengono impostati nel webclient prima di eseguire la webRequest.

Giocando con il parametro Domain, possiamo andare a filtrare i cookie che ci interessano per la chiamata in questione.