ASP.NET MVC – Caricare un file JAVASCRIPT EMBEDDED.

25 luglio 2012

Appunto qui per ricordarmene in futuro (visto che dimentico tutto ultimamente). Ho realizzato una libreria di classi in cui, oltre ad oggetti C#, sono presenti diversi file JAVASCRIPT il cui contenuto è necessario perché le classi possano funzionare correttamente all’interno di un’APP MVC.

Come è possibile caricare questi file all’interno di un progetto di questo tipo? Con pochi è semplici passaggi il lavoro è fatto.

  1. Selezionare il file JAVASCRIPT che è necessario importare all’interno del progetto libreria.
  2. Dal menu contestuale (click destro del mouse), selezionare la voce Proprietà.
  3. Nella finestra delle Proprietà modificare Build Action: Embedded Resource.
  4. Nel file AssemblyInfo.cs della del progetto libreria aggiungere una nuova riga di codice:
    [assembly: System.Web.UI.WebResource("YourNamespace.Resource.js, "application/x-javascript")]

Questi passaggi bastano per poter incorporare la libreria all’interno di un’applicazione ASP.NET Web Forms utilizzando ClientScript.GetWebResourceUrl(Type, resourceName), con MVC è necessario eseguire ulteriori passaggi per poter rendere tutto funzionante. Realizziamo per questo scopo un oggetto con cui deve essere possibile ottenere l’URL dei file embedded molto semplicemente.

public static class ResLocator
{
	private const string UrlLocatorMethodName = "GetWebResourceUrlInternal";

	public static string Resolve(Type assemblyObjectType, string resourceName) 
	{
		MethodInfo resourceLocatorMethod = assemblyObjectType.GetMethod(UrlLocatorMethodName, 
			BindingFlags.NonPublic | BindingFlags.Static);
		string url = string.Format("/{0}", resourceLocatorMethod.Invoke(
			null,
			new object[] { Assembly.GetAssembly(assemblyObjectType), resourcePath, false })
		);

		return url;
	}
}

Il metodo Resolve accetta in input un Type che deve necessariamente riferirsi ad un tipo di oggetto contenuto nel progetto libreria e resourceName, cioè il nome della risorsa da includere. Successivamente, referenziando il namespace in cui è contenuto l’helper all’interno della nostra MasterPage, è possibile includere lo script di riferimento in questo modo:

<script 
	type="text/javascript" 
	src="@ResLocator.Resolve(typeof(SampleAssemblyObject), "YourNamespace.Resource.js)" />

Completati questi passaggi ed avviata la nostra applicazione MVC, il risultato dell’inclusione è simile a questo:

/WebResource.axd?d=HWyLh7g77XDqhYfNG0fioE3hSIzYB4uYmoUs3cJSuFRbnk9cZT1AWVuijZ81&t=634788092174760080

Spero possa essere utile anche ad altri per eventuali EMBEDDED RESOURCES!

ASP.NET MVC – ViewData vs TempData

30 luglio 2010

Questo Framework mette a disposizione diverse funzionalità, una delle tante, che ritengo davvero molto utile, è la possibilità di far transitare dati tra diverse richieste http, questa possibilità si materializza attraverso l’utilizzo di diverse proprietà all’interno della pagina web: ViewData e TempData. Non nascondo la mia iniziale confusione nel capire a cosa servissero due variabili che hanno quasi lo stesso nome, ma poi tutto ha avuto un senso più chiaro e logico!

In due parole: TempData è come ViewData con una sola differenza: TempData può contenere informazioni che vengono conservate per le (massimo) due richieste successive, oltre, il suo contenuto viene automaticamente distrutto. Quindi può essere utilizzata per passare messaggi di errore e roba del genere tra diverse Action, mentre ViewData permette un interazione diretta tra Controller e View.

un mio amico ha chiarito ancor meglio il concetto: in parole povere, il tempdata permette di passare dati tra controller mentre il viewdata è adibito a quelle logiche che restano racchiuse nella stretta corrispondenza view-cotroller (yetanothergeek).

Un esempio concreto può chiarire il concetto, supponiamo di dover implementare due diversi comportamenti:

  • Un login.
  • Il caricamento di dati all’interno di DropDownList presenti in una View.

Per quanto riguarda il Login supponiamo di avere un UserController che implementa diverse view tra le quali c’è appunto Login:

public class UserController {
    ...
    public ActionResult Login(string username, string password) {
	if(!IFormProvider.Authenticate(username, password))
             TempData["LoginErrors"] = "Autenticazione fallita.";
	return RedirectToAction("Index", "Home");
    }
}

La view non è fisicamente presente ma si occupa di verificare che username e password inseriti siano validi, nel caso in cui questa condizione non si verifichi, viene caricato un valore in TempData, LoginErrors, che contiene un avviso per l’utente, successivamente viene effettuato un RedirectToAction che realmente equivale ad un Response.Redirect, se avessimo utilizzato ViewData per memorizzare questo messaggio di errore, la Redirect non ci avrebbe permesso di memorizzare l’avviso per l’utente, TempData invece mantiene lo stato dell’informazioni per la prossima richiesta e se necessario anche per la successiva! Quindi è chiaro che sarà utile per noi sfruttare questo contenitore quando sappiamo di dover spostarci da una Action ad un’altra.

Ora passiamo al secondo esempio in cui è necessario passare dal Controller, informazioni aggiuntive alla View (supponendo di avere già una View con un Model fortemente tipizzato), sempre in UserController abbiamo un’altra view che chiamiamo List, in questa, sono presenti l’elenco completo degli utenti ed un filtro di ricerca con possibilità di filtrare per Ruolo, quindi, oltre a fornire l’elenco degli utenti trovati, dobbiamo fornire un elenco di ruoli che devono essere caricati nella DropDownList:

public class UserController {
    private DatabaseDataContext db = new DatabaseDataContext();
    ...
    private void BindRequiredData() {
	// Elenco completo dei ruoli
	ViewData["Roles"] = new SelectList(db.Roles, "RoleID", "Name");
    }

    [HttpGet]
    public ActionResult List() {
        BindRequiredData();
    }

    [HttpPost]
    public ActionResult List(int RoleID) {
	BindRequiredData();

        // Prelevo gli utenti.
        var users = db.Users.Where(u => u.RoleID == RoleID);

        return View(users.ToList());
    }
}

[ad#ad-lungo]

Ho implementato ambedue le viste, GET e POST, ho preparato un metodo privato che provvede a caricare in ViewData l’elenco dei Ruoli, successivamente nella pagina web posso sfruttare questa informazioni per la generazione della DropDownList. In questo caso si nota come l’utilizzo di ViewData è più adatto al contesto, il suo ciclo di vita equivale a quello della richiesta che, una volta completata, rilascerà ogni risorsa utilizzata.

Condivido queste informazioni in quanto io per prima mi sono ritrovato ad avere falsi problemi di ViewData non funzionante, pretendevo di poter trasmettere messaggi tra diverse Action utilizzando direttamente questo contenitore, poi scoprendo TempData ed analizzando analogie e differenze, tutto è risultato molto più chiaro!

Progetti ASP.NET MVC 2.0 – Default.aspx mancante, come risolvere?

23 giugno 2010

Chi ha lavorato con progetti ASP.NET MVC 1.0 e successivamente è passato alla versione 2.0, avrà sicuramente notato la mancanza (quando viene creato un nuovo progetto) della pagina principale nella ROOT dell’applicazione, sto proprio parlando del file Default.aspx con relativo Designer e *.cs . Cosa si dice a riguardo sulla rete?

Viene spiegato che la gestione del Redirect è mantenuta direttamente dalla Pipeline di Internet Information Service 7 (IIS 7) ove questo sia installato.

Ciò significa che se pubblichiamo la nostra applicazione su un hosting in cui IIS 7 non è presente ci ritroveremo a dover gestire errori molto semplici del tipo Accesso negato (Errore 403), “e giustamente!” io direi! Cercando di accedere ad un indirizzo http://www.pippo.com, non essendoci un servizio preposto al Routing automatico, riceviamo questo messaggio in quanto l’accesso diretto alla cartella ROOT del sito ci è negato!

La soluzione è più semplice di quanto si possa pensare. Dobbiamo semplicemente imparare da MVC 1.0 alcune cose che ci tornano utili nella versione 2. Sto parlando di ricreare semplicemente questo file, Default.aspx, con le stesse caratteristiche che riporta nella versione precedente. Procediamo subito aggiungendo al nostro progetto una nuova pagina (scusate la ripetizione, ma la chiarezza è fondamentale, la pagina dovrà chiamarsi Default.aspx).

Apriamo Default.aspx e cancelliamo tutto lasciando solo la direttiva @Page come nell’esempio:

<%@ 	Page 
	Language="C#" 
	AutoEventWireup="true" 
	CodeBehind="Default.aspx.cs" 
	Inherits="ProjectName._Default" %>

[ad#ad-lungo]

Successivamente dobbiamo intervenire sul file di codice: Default.aspx.cs

using System.Web;
using System.Web.Mvc;
using System.Web.UI;

namespace ProjectName
{
    public partial class _Default : Page
    {
        public void Page_Load(object sender, System.EventArgs e)
        {
            string originalPath = Request.Path;
            HttpContext.Current.RewritePath(Request.ApplicationPath, false);
            IHttpHandler httpHandler = new MvcHttpHandler();
            httpHandler.ProcessRequest(HttpContext.Current);
            HttpContext.Current.RewritePath(originalPath, false);
        }
    }
}

Ora siamo pronti per ripubblicare il nostro progetto per poterlo vedere funzionare senza alcun problema! Ve lo avevo detto che era davvero semplice. Chi lavora con IIS è a conoscenza del fatto che al suo interno è configurato per avere dei Default come pagine web da aprire in caso di incognita, prendo ad esempio Aruba.it che nella sua documentazione specifica l’elenco dei nomi di pagina validi per poter essere automaticamente gestiti dal web server, noi abbiamo creato un file con le stesse regole in cui successivamente viene introdotto il Routing per l’avvio del vero sito web.

ASP.NET MVC – Alternative al Membership Provider

11 maggio 2010

Mi sono avvicinato da pochissimo tempo (grazie al consiglio di un guru del campo nonchè mio carissimo amico) al mondo di ASP.NET MVC. Ho pensato di utilizzare questa tecnologia per un nuovo progetto e durante il suo utilizzo, per diverse necessità ho modificato il comportamento di alcune delle sue parti. In questo articolo non voglio parlarvi della piattaforma di cui si possono trovare tantissime informazioni sul web, voglio invece fornirvi informazioni in più su alcuni aspetti di questo strumento.

Parlo del Membership Provider, un servizio integrato che permette di gestire utenti all’interno della nostra applicazione. Il punto debole è che non funziona più se questa deve essere installata su un hosting (come Aruba), in cui non è possibile gestire account di accesso dbo al server di database. Come operare?

La prima cosa da fare è sostituire l’attributo Authorized messo a disposizione dal Membership con una versione personalizzata di cui segue il codice:

using System.Web.Mvc;
public class AuthorizeAttribute
	: FilterAttribute, IAuthorizationFilter
{
	public AuthorizeAttribute()
    		: base()
        {

        }

        #region IAuthorizationFilter Membri di

        public void OnAuthorization(
		AuthorizationContext filterContext)
        {
            if (!filterContext.HttpContext.Request.IsAuthenticated)
            {
                FormsAuthentication.RedirectToLoginPage();
            }
        }
        #endregion
}

Successivamente è necessario modificare tutti le azioni (ActionResult) il cui utilizzo è riservato ai soli utenti autorizzati. Queste dovrebbero già avere un attributo, AuthorizeAttribute che deve essere sostituito con la nuova versione Authorize:

public class ProductsController : Controller
{
	// GET: /Products/Edit/5
	[Authorize]
	[AcceptVerbs(HttpVerbs.Get)]
	public ActionResult Edit(int ProductID)
	{
	    PremiereDataContext context = new PremiereDataContext();
	    Products product = context.GetProductById(ProductID);

	    return View(product);
	}
}

Il funzionamento è semplice, d’ora in poi, sarà il nostro attributo personalizzato a definire le regole per la login, sorge spontanea solo una domanda: ma come viene implementato il meccanismo di autenticazione?. Semplice, dobbiamo riscrivere la classe AccountController, ovviamente solo se vi fidate di me :D , di seguito la nuova implementazione da utilizzare:

[HandleError]
public class AccountController : Controller
{
	public AccountController() { }

	public ActionResult LogOn()
	{
	    return View();
	}

	[AcceptVerbs(HttpVerbs.Post)]
	public ActionResult LogOn(
		string userName, string password,
		bool rememberMe, string returnUrl)
	{
	    if(!FormsAuthentication.Authenticate(userName, password))
	    {
		return View();
	    }
	    FormsAuthentication.SetAuthCookie(userName, rememberMe);
	    if (!String.IsNullOrEmpty(returnUrl))
	    {
		return Redirect(returnUrl);
	    }
	    else
	    {
		return RedirectToAction("Index", "Home");
	    }
	}

	public ActionResult LogOff()
	{
	    FormsAuthentication.SignOut();

	    return RedirectToAction("Index", "Home");
	}
}

Si, rispetto al vecchio Membership c’è davvero poca roba, il metodo LogOn si occupa dell’autenticazione ed utilizzando l’oggetto (FormsAuthentication) già presente nel framework, è possibile implementare un autenticazione basata sulla configurazione dell’applicazione stessa, altra domanda: dove vengono salvate le credenziali di accesso? La risposta: web.config:

<authentication mode="Forms">
	<forms timeout="2880" loginurl="~/Account/LogOn">
		<credentials passwordformat="Clear">
			<user password="prova" name="Roberto" />
		</credentials>
	</forms>
</authentication>

Ho utilizzato un passwordformat di tipo Clear (informazioni salvate in chiaro) ma è comunque poissibile utilizzare altri due formati: MD5 ed SHA1.

Concludendo, questa può essere una buona soluzione se dobbiamo creare un sito in cui le utenze non rivestono un particolare ruolo ma semplicemente la funzione di verifica per l’accesso al back-end (es. siti vetrina in cui l’unico utente esistente sarà il proprietario stesso del sito che dovrà occuparsi della manutenzione). Come avrete notato non è una tecnica invasiva, il codice di tutte le viste rimane quasi invariato, abbiamo solo reimplementato l’AccountController. Spero che queste informazioni siano state per voi utili, quando ho dovuto cercare una soluzione alternativa al Membership Provider ammetto di essere impazzito all’inizio!