martedì 30 settembre 2014

Creare un Work Item con le REST API - Visual Studio Online

A volte può essere utile aggiungere un nuovo Work Item ad un nostro Team Project in modo automatico, magari in risposta ad un determinato evento.

Le nuove "WIT REST API v1.0 (preview 2)" (rilasciate il 4 settembre) esposte da Visual Studio Online ci permettono di farlo.

Quando creiamo un work item, possiamo indicare i valori per qualsiasi tipo di work item fields.

Per creare un work item, è necessario effettuare una richiesta HTTP PATCH a:

https://your_account.visualstudio.com/defaultcollection/team_project_name/_apis/wit/workitems/$work_item_type_name?api-version=1.0-preview.2

Il body della richiesta deve essere valorizzato in base a questo formato:

[
    {
        "op": "add",
        "path": { string }
        "value": { string or int, depending on the field }
    },
    {
        "op": "add",
        "path": "/relations/-",
        "value":
        {
            "rel": { string },
            "url": { string },
            "attributes":
            {
                { name/value pairs }
            }
        }
    }
]

Un esempio di richiesta potrebbe essere:

https://myAccount.visualstudio.com/defaultcollection/myProject/_apis/wit/workitems/$task?api-version=1.0-preview.2

[
  {
    "op": "add",
    "path": "/fields/System.Title",
    "value": "Change blog title height"
  }
]

Questa richiesta produce una response come la seguente, in cui si possono trovare tutte le informazioni relative al work item appena creato:

{
  "id": 88,
  "rev": 1,
  "fields": {
    "System.AreaPath": "myProject",
    "System.TeamProject": "myProject",
    "System.IterationPath": "myProject",
    "System.WorkItemType": "Task",
    "System.State": "To Do",
    "System.Reason": "New task",
    "System.CreatedDate": "2014-09-30T10:25:12.943Z",
    "System.CreatedBy": "Davide Benvegnu",
    "System.ChangedDate": "2014-09-30T10:25:12.943Z",
    "System.ChangedBy": "Davide Benvegnu,
    "System.Title": "Change blog title height"
  },
  "_links": {
    "self": {
      "href": "https://myAccount.visualstudio.com/DefaultCollection/_apis/wit/workItems/88"
    },
    "workItemUpdates": {
      "href": "https://myAccount.visualstudio.com/DefaultCollection/_apis/wit/workItems/88/updates"
    },
    "workItemRevisions": {
      "href": "https://myAccount.visualstudio.com/DefaultCollection/_apis/wit/workItems/88/revisions"
    },
    "workItemHistory": {
      "href": "https://myAccount.visualstudio.com/DefaultCollection/_apis/wit/workItems/88/history"
    },
    "html": {
      "href": "https://myAccount.visualstudio.com/web/wi.aspx?pcguid=0fa87894-6f48-4458-957d-3438b6bb9343&id=88"
    },
    "workItemType": {
      "href": "https://myAccount.visualstudio.com/DefaultCollection/c4637008-2068-4b3f-828d-a214e2ba5210/_apis/wit/workItemTypes/Task"
    },
    "fields": {
      "href": "https://myAccount.visualstudio.com/DefaultCollection/_apis/wit/fields"
    }
  },
  "url": "https://myAccount.visualstudio.com/DefaultCollection/_apis/wit/workItems/88"
}

venerdì 12 settembre 2014

A proposito di Azure Websites Extensions

Ogni Azure Website fornisce un end point di management estendibile che permette di sfruttare un potente set di strumenti, deployato come site extensions. Questi strumenti spaziano dagli editor di codice sorgente come Visual Studio Online a strumenti di gestione delle risorse connesse, come un database MySQL collegato ad un sito web. 

Le Site Extensions sono, di fatto, delle web app con dei metadati che vengono usati per la registrazione dell'estensione; possono essere create per ogni development stack supportato dalla piattaforma degli Azure Websites.

Le Site Extesions esistenti sono disponibili, per ogni WebSite, all'interno dell'Azure Preview Portal:


Per aggiungere una nuova estensione sito basta andare alla sezione Configurazione nella "pagina" del web site, fare clic sul pulsante ADD e selezionare un'estensione dalla lista.  Ognuna di queste estensioni è resa disponibile dal publisher riportato sotto il nome dell'extension e con i temini legali che devono essere accettati esplicitamente prima dell'installazione.


Una volta aggiunta, il contenuto della Site Extension viene copiato dentro la cartella %HOME%\SiteExtensions parallela alla website root. Attenzione che l'aggiunta di un'estensione fa riavviare il sito.


È possibile anche creare delle nuove estensioni.
Se risultano necessari strumenti che non sono già presenti, è possibile creare nuove Site Extension da utilizzare con i WebSite seguendo le istruzioni a questo link
È inoltre possibile pubblicare nuove estensioni in modo che siano disponibili per tutta la piattaforma Azure WebSites attraverso il portale "Site Extension Gallery submission": http://www.siteextensions.net.

martedì 19 agosto 2014

Azure Web Sites Deploy: come funziona?

Gli Azure Web Sites supportano il continuous deployment da strumenti del controllo del codice sorgente e repository com BitBucket, CodePlex, Dropbox, Git, GitHub, Mercurial e TFS/VSO. È possibile infatti utilizzare questi strumenti per manutenere i contenuti ed i sorgenti del sito e poi pubblicare le modifiche in modo semplice e veloce.

Metodi di deploy supportati
Usando un repository Git locale è possibile, ad esempio, fare la "promozione" manuale degli aggiornamenti da un progetto locale ad un Azure Web Site;  deployando invece da strumenti come BitBucket, CodePlex, Dropbox, GitHub, TFS/VSO o Mercurial è possibile abilitare un processo di continuous deployment in cui sarà Azure stesso a fare il pull update più recenti del progetto.

Entrambi i metodi permettono di deployare i progetti su un Azure Web Site, ma il continuous deployment è utile quando ci sono più persone che lavorano su un progetto e ci si vuole assicurare che l'ultima versione sia sempre pubblicata indipendentemente da chi ha fatto l'aggiornamento più recente. Il continuous deployment è anche utile se viene utilizzato uno degli strumenti di cui sopra come repository centrale per l'applicazione.

Su internet ci sono un sacco di articoli che spiegano come fare il deploy di un Azure WebSite (ad esempio http://azure.microsoft.com/en-us/documentation/articles/web-sites-deploy/) o come implementare delle strategia di Continuous Deployment (es. http://azure.microsoft.com/en-us/documentation/articles/web-sites-publish-source-control/).

Ma come funziona in realtà "dietro le quinte"?

La risposta è "Kudu".

Kudu è il motore che sta dietro il deployments degli Azure Web Sites, ma può anche funzionare al di fuori di Azure.
Ha un'architettura alquanto insolita, nel senso che è un servizio single-tenant anziché multi-tenant. Questo significa che ogni sito Web Azure ha una propria istanza del servizio Kudu, completamente distinta dalle istanze Kudu utilizzate per altri siti Azure.

Il componente rimane attivo in background e "controlla" se si verificano checkin, commit, nuovi file, build e così via. Quando rileva qualcosa, KuduSync inizia e fare il "lavoro sporco".

Si tratta di uno strumento piuttosto interessante:
  • è un progetto open source disponibile su GitHub (https://github.com/projectkudu/kudu)
  • è installato automaticamente per ogni Azure Web Sites
  • può utilizzare uno script di distribuzione personalizzato

Ma la cosa più importante (imho) è questa:
Il deployment viene creato nella struttura delle cartelle del sito web e il nuova deployment viene copiato nella root del sito, lasciando intatti i vecchi deployment. 
Questo significa che è possibile fare il "rollback" a qualsiasi deploy fatto in passato!

È anche possibile accedere alla dashboard web di Kudu, utilizzando un url  del tipo "https://your_website_name.scm.azurewebsites.net/" e le deployment credentials associate (oppure le credenziali di un amminisistratore del servizio).

Dashboard di Kudu

Nella dashboard di Kudu è possibile trovare un sacco di informazioni utili sull'ambiente del tuo sito web insieme a una serie di strumenti per gestire il sito e, ultimo ma non meno importante, un set completo di API REST. C'è anche la possibilità di gestire le WebSite extension.

C'è anche un interessante video (in inglese) in cui David Ebbo e Scott Guthrie spiegano come funziona Kudu: http://channel9.msdn.com/Shows/Azure-Friday/What-is-Kudu-Azure-Web-Sites-Deployment-with-David-Ebbo

mercoledì 6 agosto 2014

Azure WebSite, Cloud Service e Virtual Machine: quale scegliere?

Capita, a volte, di dover iniziare un nuovo sviluppo o di dover pianificare una pubblicazione sul cloud, su Azure, ma di non sapere nel dettaglio che tipo di servizio è meglio utilizzare: dovrei usare un WebSite, un Cloud Service o una Virtual Machine? Che tipi di vantaggi / svantaggi ho in un caso o nell'altro?

Vediamo quelle che sono le principali differenze.

Attenzione: questo post è aggiornato con le info disponibili al 5 agosto 2014.

Innanzitutto uno schema, che usa Microsoft nella documentazione ufficiale di Azure, per comparare i 3 servizi:

Courtesy of Microsoft

In questa immagine è abbastanza chiaro il livello di semplicità vs possibilità di controllo che offrono le tre soluzioni. Volendo essere estremamente sintetici:

  • WebSite:
    • Soluzione estremamente semplice da utilizzare, che offre comunque un buon set di strumenti a supporto (monitoring, alerts, scale, ecc) ma a discapito del livello di personalizzazione e controllo sulla configurazione.
    • In applicazioni multi-tier, fornisce il supporto alla solo web tier
  • Cloud Service:
    • Meno semplice da utilizzare e deployare rispetto ai Websites, fornisce un livello di controllo molto maggiore.
    • Anche in questo caso ci sono diversi strumenti di monitoraggio e supporto già inclusi
    • È possibile utilizzare sia i WebRole (che di fatto sono delle VM dedicate con installato IIS) sia i WorkerRole (sempre delle VM dedicate, ma senza IIS. È possibile paragonare un WorkerRole ad un Windows Service)
    • In scenari multi-tier, è possibile utilizzare una combinazione di WebRole e WorkerRole per implementare sia il web tier che il middle-tier che il backend 
    • In scenari multi-tier, è possibile scalare il frontend ed il backend in modo indipendente
  • Virtual Machine:
    • Lascia la configurazione e la personalizzazione completamente in mano all'utente, quindi è più complicata da gestire ma fornisce il massimo livello di controllo possibile, come con i server on-premises.
    • Si possono utilizzare per qualsiasi tipo di applicazione ed architettura
    • È necessario occuparsi manualmente della gestione del sistema, quindi anche di aggiornamenti, policy di sicurezza ecc ecc
    • È la scelta ideale in scenari particolarmente complessi o quando si ha necessità di hostare servizi o software che non sono supportati nelle altre modalità.

Questa è la tabella ufficiale con la comparazione dei tre tipi di servizio:

FEATUREWEB SITESCLOUD SERVICES
(WEB ROLES)
VIRTUAL MACHINES
Access to services like Service Bus, Storage, SQL Database
XXX
Host web or web services tier of a multi-tier architecture
XXX
Host middle tier of a multi-tier architecture
XX
Integrated MySQL-as-a-service support
X1X
Support for ASP.NET, classic ASP, Node.js, PHP, Python
XXX
Scale out to multiple instances without redeploy
XX2
Support for SSL
3XX
Visual Studio integration
XXX
Remote Debugging
XXX
Deploy code with TFS
XXX
Deploy code with GIT, FTP
XX
Deploy code with Web Deploy
X4X
WebMatrix support
XX
Near-instant deployment
X
Instances share content and configuration
X
Scale up to larger machines without redeploy
X
Multiple deployment environments (production and staging)
XX
Network isolation with Azure Virtual Network
XX
Support for Azure Traffic Manager
XXX
Remote desktop access to servers
XX
Ability to define/execute start-up tasks
XX
Automatic OS update management
XX
Integrated Endpoint Monitoring
XXX
Seamless platform switching (32bit/64bit)
XX
1 Web or worker roles can integrate MySQL-as-a-service through ClearDB's offerings, but not as part of the Management Portal workflow.
2 Although Virtual Machines can scale out to multiple instances, the services running on these machines must be written to handle this scale-out. An additional load balancer must be configured to route requests across the machines. Finally, an Affinity Group should be created for all machines participating in the same role to protect them from simultaneous restarts from maintenance or hardware failures.
3 For Web Sites, SSL for custom domain names is only supported for standard mode. For more information on using SSL with Web Sites, see Configuring an SSL certificate for an Azure Web Site.
4 Web Deploy is supported for cloud services when deploying to single-instance roles. However, production roles require multiple instances to meet the Azure SLA. Therefore, Web Deploy is not a suitable deployment mechanism for cloud services in production.

Per vedere la comparazione completa dei tre servizi, costantemente aggiornata, è possibile consultare la guida ufficiale sulla documentazione di Azure

mercoledì 30 luglio 2014

Indirizzo IP dedicato sugli Azure Website

Quando si ha un indirizzo IP condiviso con altri siti / clienti (come accade in un ambiente multi-tenant), si possono avere alcuni problemi come, per esempio, vedersi inserito l'IP in una blacklist a causa del contenuto di altri siti.

L'unico modo affidabile per risolvere questo ed altri problemi è quello di configurare il website con un IP dedicato. Ciò significa che il sito utilizzerà un proprio IP, che quindi non sarà condiviso con altri siti. 

In Azure, è possibile facilmente ottenere un IP dedicato con l'attivazione dell'IP SSL. Questa opzione è disponibile solo per i siti del tier Standard e se il website utilizza un dominio personalizzato ci sono alcune considerazioni supplementari.

Se si utilizza un dominio personalizzato ed è stato configurato un record CNAME che punta al sito su Azure (ad esempio, mysite.azurewebsites.net), allora è piuttosto semplice - basta cambiare il record con il proprio provider DNS e quindi configurare IP-SSL.

Se, invece, è stato configurato un record A per risolvere l'hostname è necessario seguire questa procedura:

  1. Cambiare la mappatura dell'hostname (cioè www.mysite.com) da un record a un CNAME che punta al Website su Azure (cioè mysite.azurewebsites.net). Questa operazione non dovrebbe causare nessun downtime visto che entrambi i record risolveranno lo stesso indirizzo IP. Attendere un po' di tempo per essere sicuri che i DNS si siano replicati correttamente.
  2. Caricare un certificato valido per www.mysite.com sul Website utilizzando la pagina "Domains" nella tab "Configure". Normalmente il certificato va acquistato da un provider abilitato (una certification authority), ma se non si intende utilizzare effettivamente SSL, è possibile utilizzare un certificato auto-firmato che è facile da generare e, soprattutto, è gratis.
  3. Configurare un binding SSL IP Based per www.mysite.com. Questa opzione è disponibile sotto "SSL Binding" nella scheda "Configure". Per maggiori informazioni è possibile consultare la sezione "Configure SSL" nella guida per attivare l'SSL su Azure.

martedì 22 luglio 2014

Visual Studio Online ora supporta Azure Active Directory


Ieri il team vsalm di Microsoft ha iniziato il deploy dello sprint 68.

La parte più grande ed interessante dell'annuncio è la nuova fase del supporto di Azure Active Directory in VS Online. Hanno iniziato questo "viaggio" in maggio quando hanno annunciato le prime parti del supporto all'AAD a "Build". Poi hanno aggiunto alcune (poche) altre informazioni durante "TechEd" ma era una cosa passata un po' in sordina visto che, fino a questa settimana, non era possibile convertire o associare un account esistente ad AAD. Con questo deploy, invece, diventa possibile! Ufficialmente è in preview e quindi è necessario richiedere l'accesso alla funzionalità, ma sembra accettino tutte le richieste quindi non dovrebbe essere un grosso problema (anche perchè va fatto una volta sola). 

Con queste modifiche, è possibile:

  • Associare un OrgID (ovvero credenziali AAD/AD) alla sottoscrizione MSDN, se ce n'è una, ed usarla come grant per la licenza VSO
  • Creare un nuovo account collecato ad AAD
  • Collegare un account esistente ad AAD
  • Scollegare un account da AAD
  • Accedere sia con un Microsoft Account che con un OrgID (solo AAD oppure in sycn con l'Active Directory on premises) creando di fatto un SSO con le credenziali corporate, Office 365, ecc.



Per vedere tutti i dettagli riguardo il supporto AD e le altre cose incluse nell'update è possibile leggere il post originale di Brian Harry su MSDN.

lunedì 14 luglio 2014

ASP.net MVC ActionLink con un'Immagine: Parte 3 (Ajax)

Nei mie precedenti post abbiamo visto come implementare alcuni helper custom per aggiungere dei Glifi Bootstap o delle Immagini agli ActionLinks.

Ora invece vedremo come fare più o meno la stessa cosa ma aggiungendo il supporto Ajax.

Sostanzialmente quello che bisogna fare è costruire l'html tenendo conto di tutte le "cose" relative ad ajax (attributi, parametri, ecc) che il normale helper Ajax.ActionLink aggiunge.

In questo esempio ho usato la versione che utilizza i glifi, ma è ovviamente possibile usare lo stesso approccio anche nel caso si vogliano usare immagini normali.

/// <summary>
/// Create an Ajax.ActionLink with an associated glyphicon
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="linkText"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <param name="glyphicon"></param>
/// <param name="routeValues"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcHtmlString ImageActionLink(this AjaxHelper ajaxHelper, string linkText, string actionName, string controllerName, string glyphicon, AjaxOptions ajaxOptions, RouteValueDictionary routeValues = null, object htmlAttributes = null)
{
 //Example of result:           
 //<a id="btnShow" href="/Customers/ShowArtworks?customerId=1" data-ajax-update="#pnlArtworks" data-ajax-success="jsSuccess" 
 //data-ajax-mode="replace" data-ajax-method="POST" data-ajax-failure="jsFailure" data-ajax-confirm="confirm" data-ajax-complete="jsComplete" 
 //data-ajax-begin="jsBegin" data-ajax="true">
 //  <i class="glyphicon glyphicon-pencil"></i>
 //  <span>Edit</span>
 //</a>

 var builderI = new TagBuilder("i");
 builderI.MergeAttribute("class", "glyphicon " + glyphicon);
 string iTag = builderI.ToString(TagRenderMode.Normal);

 string spanTag = "";
 if (!string.IsNullOrEmpty(linkText))
 {
  var builderSPAN = new TagBuilder("span");
  builderSPAN.InnerHtml = " " + linkText;
  spanTag = builderSPAN.ToString(TagRenderMode.Normal);
 }

 //Create the "a" tag that wraps
 var builderA = new TagBuilder("a");

 var requestContext = HttpContext.Current.Request.RequestContext;
 var uh = new UrlHelper(requestContext);

 builderA.MergeAttribute("href", uh.Action(actionName, controllerName, routeValues));

 //Ajax section
 builderA.MergeAttribute("data-ajax", "true");
 builderA.MergeAttribute("data-ajax-update", ajaxOptions.UpdateTargetId.StartsWith("#") ? ajaxOptions.UpdateTargetId : "#" + ajaxOptions.UpdateTargetId);
   
 if (!string.IsNullOrEmpty(ajaxOptions.InsertionMode.ToString()))
  builderA.MergeAttribute("data-ajax-mode", ajaxOptions.InsertionMode.ToString());            
 
 if (!string.IsNullOrEmpty(ajaxOptions.OnBegin))
  builderA.MergeAttribute("data-ajax-begin", ajaxOptions.OnBegin);
 
 if (!string.IsNullOrEmpty(ajaxOptions.OnComplete))
  builderA.MergeAttribute("data-ajax-complete", ajaxOptions.OnComplete);
   
 if (!string.IsNullOrEmpty(ajaxOptions.OnFailure))
  builderA.MergeAttribute("data-ajax-failure", ajaxOptions.OnFailure);
   
 if (!string.IsNullOrEmpty(ajaxOptions.OnSuccess))
  builderA.MergeAttribute("data-ajax-success", ajaxOptions.OnSuccess);
   
 if (!string.IsNullOrEmpty(ajaxOptions.Confirm))
  builderA.MergeAttribute("data-ajax-confirm", ajaxOptions.Confirm);
  
 if (!string.IsNullOrEmpty(ajaxOptions.HttpMethod))
  builderA.MergeAttribute("data-ajax-method", ajaxOptions.HttpMethod);

 if (htmlAttributes != null)
 {
  IDictionary<string, object> attributes = new RouteValueDictionary(htmlAttributes);
  builderA.MergeAttributes(attributes);
 }

 builderA.InnerHtml = iTag + spanTag;

 return new MvcHtmlString(builderA.ToString(TagRenderMode.Normal));
}


Come si può vedere, il codice è simile a quello che abbiamo utilizzato nei post precedenti con l'eccezione del fatto che stiamo estendendo un "AjaxHelper" invece che un "HtmlHelper" come accadeva prima e che abbiamo aggiunto una "ajax section".

giovedì 10 luglio 2014

Modifiche alle licenze di Visual Studio Online (in meglio)

La scorsa primavera Visual Studio Online ha visto la sua "promozione" da "Preview" a "General Availability". Tale processo ha incluso modifiche al branding, gli SLA, l'annuncio dei prezzi, la fine del programma early adopter e altro ancora.

Ora, il team VSO / TFS ha deciso di apportare questi due importanti cambiamenti al licensing (indicativamente nei prossimi due mesi):


  • qualsiasi account VS Online sarà in grado di avere un numero illimitato di utenti "stakeholder" con accesso ad un sottoinsieme di funzionalità, senza alcun costo aggiuntivo. 
  • il piano Visual Studio Online Advanced comprenderà l'accesso a tutte le funzionalità del Test hub.


Il team sta lavorando sodo per implementare questi cambiamenti di licenza e l'aspettativa è che avranno circa 2 sprint da copmletare per dinire tutto. Il che posizionerebbe la data effettiva di rilascio attorno a metà agosto.

In generale, l'obiettivo del team è quello di mantenere le licenze per VS Online e Team Foundation Server più "parallele" possibili - per limitare il grado di complessità. Come risultato, stanno evolvendo l'attuale esenzione dalle CAL di TFS "Work Item Web Access" per allinearla al modello di "stakeholder" di VSO. Ciò si tradurrà in più funzionalità a disposizione degli utenti TFS senza CAL. La speranza è di ottenere questo cambiamento a partire da Team Foundation Server 2013 Update 4.

Per leggere l'annunco originale di Brian Harry, visitate:
http://blogs.msdn.com/b/bharry/archive/2014/07/09/upcoming-vs-online-licensing-changes.aspx

mercoledì 9 luglio 2014

ASP.net MVC ActionLink con un'Immagine: Parte 2 (Immagine)

Nel mio precedente post ho parlato di come creare un helper custom per renderizzare un ActionLink con un glifo.

In questo post, invece, vedremo come creare un helper simile ma che usa delle immagini "normali" al posto dei bootstrap glyphs.

/// <summary>
/// Create an ActionLink with an associated image
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="linkText"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <param name="imagePath"></param>
/// <param name="routeValues"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcHtmlString ImageImgActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string imagePath, object routeValues = null, object htmlAttributes = null)
{
 //Exemple of result:
 //<a href="@Url.Action("Edit", new { id = Model.id_rod })">
 //  <i class="glyphicon glyphicon-pencil"></i>
 //  <span>Edit</span>
 //</a>

 if (imagePath.StartsWith("~/"))
 {
  imagePath = VirtualPathUtility.ToAbsolute(imagePath);
 }

 var builderImage = new TagBuilder("image");
 builderImage.MergeAttribute("src", imagePath);
 builderImage.MergeAttribute("alt", linkText);
 builderImage.MergeAttribute("style", "border=0");
 string imageTag = builderImage.ToString(TagRenderMode.SelfClosing);

 string spanTag = "";
 if (!string.IsNullOrEmpty(linkText))
 {
  var builderSPAN = new TagBuilder("span");
  builderSPAN.InnerHtml = " " + linkText;
  spanTag = builderSPAN.ToString(TagRenderMode.Normal);
 }

 //Create the "a" tag that wraps
 var builderA = new TagBuilder("a");

 var requestContext = HttpContext.Current.Request.RequestContext;
 var uh = new UrlHelper(requestContext);

 builderA.MergeAttribute("href", uh.Action(actionName, controllerName, routeValues));

 if (htmlAttributes != null)
 {
  IDictionary<string, object> attributes = new RouteValueDictionary(htmlAttributes);
  builderA.MergeAttributes(attributes);
 }

 builderA.InnerHtml = imageTag + spanTag;

 return new MvcHtmlString(builderA.ToString(TagRenderMode.Normal));
}


È possibile passare all'helper sia url relativi che assoluti (parametro imagePath) in modo da avere la massima flessibilità.

lunedì 7 luglio 2014

ASP.net MVC ActionLink con un'Immagine: Parte 1 (Glyph)

Se state usando ASP.net MVC saprete sicuramente che c'è un helper piuttosto utile che permette di creare dei link a delle Action dei controller: si tratta del Html.ActionLink.

È possibile utilizzare questo helper solo con link testuali, ma se invece volessimo aggiungere al link anche un glifo di bootstrap, come in questo esempio?



Beh, la risposta "fast&dirty" è quella di scrivere un po' di codice html direttamente nella pagina cshtml. Qualcosa tipo:

<a href="@Url.Action("Edit", new { id = Model.id })">
  <i class="glyphicon glyphicon-pencil"></i>
  <span>Edit</span>
</a>


Questo approccio è sicuramente più veloce di qualsiasi altro, se abbiamo bisogno di avere un comportamento di questo tipo solo in un paio di posti. Ma se avessimo necessità di avere i link con le immagini un po' ovunque nella nostra applicazione? (come ad esempio nel mio caso) Dovremmo copiare quel codice in N posti diversi e cambiarlo di volta in volta a seconda delle necessità... non è il massimo.

In questo caso, la soluzione migliore è quella di scrivere un custom helper che lo faccia per noi:

/// <summary>
/// Create an ActionLink with an associated glyphicon
/// </summary>
/// <param name="htmlHelper"></param>
/// <param name="linkText"></param>
/// <param name="actionName"></param>
/// <param name="controllerName"></param>
/// <param name="glyphicon"></param>
/// <param name="routeValues"></param>
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcHtmlString ImageActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string glyphicon, object routeValues = null, object htmlAttributes = null)
{
 //Exemple of result:
 //<a href="@Url.Action("Edit", new { id = Model.id_rod })">
 //  <i class="glyphicon glyphicon-pencil"></i>
 //  <span>Edit</span>
 //</a>

 var builderI = new TagBuilder("i");
 builderI.MergeAttribute("class", "glyphicon " + glyphicon);
 string iTag = builderI.ToString(TagRenderMode.Normal);

 string spanTag = "";
 if (!string.IsNullOrEmpty(linkText))
 {
  var builderSPAN = new TagBuilder("span");
  builderSPAN.InnerHtml = " " + linkText;
  spanTag = builderSPAN.ToString(TagRenderMode.Normal);
 }            

 //Create the "a" tag that wraps
 var builderA = new TagBuilder("a");

 var requestContext = HttpContext.Current.Request.RequestContext;
 var uh = new UrlHelper(requestContext);
 
 builderA.MergeAttribute("href", uh.Action(actionName, controllerName, routeValues));

 if (htmlAttributes != null)
 {
  IDictionary<string, object> attributes = new RouteValueDictionary(htmlAttributes);
  builderA.MergeAttributes(attributes);
 }
  
 builderA.InnerHtml = iTag + spanTag;
 
 return new MvcHtmlString(builderA.ToString(TagRenderMode.Normal));
}

A questo punto sarà possibile invocare l'helper Html.ImageActionLink con gli stessi parametri che passeremmo al normale Html.ActionLink ma con in più la classe glyphicon della glyph image che vogliamo aggiungere al nostro link.