Alpha's Manifesto

La madriguera de una insignificante figurita blanquinegra.

dnndev.me → 127.0.0.1

Dominio para desarrollos locales

En geeks.ms (via los enlaces interesantes de VariableNotFound) publicaron un artículo/tip/huevo de pascua (no lo considero un huevo de pascua) sobre dnndev.me, un dominio registrado y apuntando sus entradas DNS a 127.0.0.1. La idea detrás de esto es que los desarrolladores puedan utilizarlo para no modificar sus entradas en el archivo hosts. El nombre del dominio viene realmente de DotNetNuke Development, que es la razón que llevó a registrar este dominio. Yo me hubiera conformado con un localhost.com, o loopback.me, o algo así.

Realmente no es tan útil si uno sabe cómo editar los archivos de host, pero  – y acá lo interesante — muchas veces no tenemos accesos de administrador para lograrlo, y en ese caso es en donde necesitamos un DNS que nos devuelva 127.0.0.1 para estas cosas. Ahí es donde esto se vuelve realmente útil.

A ver quién tiene una idea similar y registra dominios para apuntarlos a direcciones de redes C. No les gustaría ir a 192-168-0-1.my.net y que tengan acceso a su router? Mejor aún, podrían usarse alias en subdominios para IPs conocidas dentro de las redes. linksysrouter.my.net, broadcast.my.net, etc. ¿Quién será el valiente?

Soy un zorrinito cobarde.

Link del día: Chirpy

Ayer hablé un ratito más con JH y siendo que no hay una sola charla que pueda tener con él en donde no aprenda algo, me comentó de su más reciente descubrimiento: Chirpy.

Chirpy es un proyecto open source de un plugin para Visual Studio que nos permite trabajar con archivos js, css, .less y T4 de forma casi nativa. Además, nos genera minimizaciones, hojas de estilo y transformaciones a medida que vamos guardando los archivos, casi “real time” a como estamos trabajando. Básicamente, nos permite extender la cantidad de herramientas que disponemos en tiempo de diseño.

No lo he probado personalmente, y no sé si es que introduzca algo de lentitud al entorno pero parece realmente convenir para tener una más variedad de posibilidades al momento de desarrollar, ahorrándonos tiempos de pruebas e integración.

Soy un zorrinito integrado.

Link del día: Diseñar con TDD

Para los que no conocen la sigla, TDD representa el concepto de Test Driven Development, la metodología de hacer las pruebas de lo que queremos lograr antes del código.

Este es el caso de alguien que estaba demostrando esa metodología ante uno de los escépticos que no creen que sea realmente aplicable. “Funciona muy bien en la teoría pero al momento de la verdad no se puede respetar totalmente”, pensaba. Ese es el caso de Rob Conery, explicando las técnicas de TDD de Brad Wilson.

Lo que él hizo fue, intencionalmente, planear una situación en un sistema medianamente complejo, para luego introducir un nuevo requerimiento que cambiaba todo el diseño. Parece que entonces lo que Wilson hizo fue, en lugar de tirar todo y comenzar de cero, ir adaptando las pruebas gradualmente, ir refactorizándolas hasta que comenzaron a formar “clases”, que luego se movieron del entorno de pruebas al sistema real.

“Un concepto en el que no había pensado nunca” – menciona Conery – “usar el archivo de pruebas como una especie de útero {de dónde nacen las clases}”.

Soy un zorrinito TDD.

Tu código es infeliz

Sad code

Ahora lo sabés: tu código es infeliz. Demostrado.

Link del día: ¿Te faltan ideas?

Este link es otro de los tantos geniales que proporciona la gente de MicroSiervos. Todavía no lo dije, pero si te gusta la ciencia en cualquiera de sus formas, realmente recomiendo que sigas a estos muchachos españoles.

El link en particular es una aplicación basada en Twitter, de mucha simpleza, pero de alto valor. La aplicación, llamada The Internet Wishlist, funciona de la siguiente manera:

  1. Yo, tú, él, nosotros, vosotros y ellos postean en Twitter usando el hashtag #theiwl. Como buen wishlist, deberías explicar qué es lo que quisieras que la internet te proveyera. Servicios, información, aplicaciones, integraciones, sistemas, etc.
  2. Esta aplicación mostrará un listado de los pedidos de la gente.
  3. El resto de nosotros tenemos ideas sobre qué cosas están faltando allá afuera
  4. Manos a la obra: programadores con trabajo, empresas con clientes, usuarios con sistemas.
  5. ?
  6. Profit!

Cabe aclarar que no todo aparece por ahí, sino que los twitts pasan por cierto filtro antes, y por último, que tienen un RSS al que podemos seguir. Totalmente recomiendo hacerlo, quién sabe qué joya estará esperando ahí afuera para que la leamos.

Soy un zorrinito con demanda.

Crear sitios de SharePoint programáticamente

El objetivo

Estuve peleando estos días con la posibilidad de crear sitios de SharePoint programáticamente desde mi código. Sin embargo, aún cuando esto fue hecho para un proyecto interno, tomé tanto de la internet que realmente siento que debo devolver algo a la comunidad. Además, no voy a divulgar nada relacionado al proyecto, así que aquí vamos.

Primero, buscando en internet una forma de crear estos sitios programáticamente, me topé con un artículo del 2007 de WinSmarts.com, llamado Programatically create a Sharepoint site based on a site definition. Aquí está el código de ese post:

public static bool CreateSite(string parentSiteURL, string siteURLRequested,
    string siteTitle, string siteTemplateName)
{
    bool returnCondition = false; // Assume failure.
    const Int32 LOCALE_ID_ENGLISH = 1033;
    using (SPSite siteCollection = new SPSite(parentSiteURL))
    {
        SPWeb parentWeb = siteCollection.OpenWeb();
        SPWebTemplateCollection Templates = siteCollection.GetWebTemplates(Convert.ToUInt32(LOCALE_ID_ENGLISH));
        SPWebTemplate siteTemplate = Templates[siteTemplateName];
        if (parentWeb.Webs[siteURLRequested].Exists)
        {
            parentWeb.Webs.Delete(siteURLRequested);
        }
        parentWeb.Webs.Add(siteURLRequested, siteTitle, "", Convert.ToUInt32(LOCALE_ID_ENGLISH), siteTemplate, false, false);

        // All is good?
        returnCondition = true;
    }
    return returnCondition;
}

Lo que yo quería cambiar de esta solución era:

  • Los Locales estaban hardcodeados
  • Los Site templates estaban hardcodeados
  • La descripción del sitio estaba hardcodeado (esa es la cadena vacía en la llamada a webs.add)

Removiendo literales…

Entonces fui a revisar la lista de locales: SharePoint Locale ID (LCID) Table, y una lista de los identificadores de site templates, en un artículo llamado Create a site programmatically in moss.

Bueno… aquí vamos (cuidado, vienen enumeraciones largas):

public enum SPLocales: uint
{
	Afrikaans /*af*/ = 1078,
	Albanian /*sq*/ = 1052,
	Arabic_UnitedArabEmirates /*ar-ae*/ = 14337,
	Arabic_Bahrain /*ar-bh*/ = 15361,
	Arabic_Algeria /*ar-dz*/ = 5121,
	Arabic_Egypt /*ar-eg*/ = 3073,
	Arabic_Iraq /*ar-iq*/ = 2049,
	Arabic_Jordan /*ar-jo*/ = 11265,
	Arabic_Kuwait /*ar-kw*/ = 13313,
	Arabic_Lebanon /*ar-lb*/ = 12289,
	Arabic_Libya /*ar-ly*/ = 4097,
	Arabic_Morocco /*ar-ma*/ = 6145,
	Arabic_Oman /*ar-om*/ = 8193,
	Arabic_Qatar /*ar-qa*/ = 16385,
	Arabic_SaudiArabia /*ar-sa*/ = 1025,
	Arabic_Syria /*ar-sy*/ = 10241,
	Arabic_Tunisia /*ar-tn*/ = 7169,
	Arabic_Yemen /*ar-ye*/ = 9217,
	Armenian /*hy*/ = 1067,
	Azeri_Latin /*az-az*/ = 1068,
	Azeri_Cyrillic /*az-az*/ = 2092,
	Basque /*eu*/ = 1069,
	Belarusian /*be*/ = 1059,
	Bulgarian /*bg*/ = 1026,
	Catalan /*ca*/ = 1027,
	Chinese_China /*zh-cn*/ = 2052,
	Chinese_HongKongSAR /*zh-hk*/ = 3076,
	Chinese_MacauSAR /*zh-mo*/ = 5124,
	Chinese_Singapore /*zh-sg*/ = 4100,
	Chinese_Taiwan /*zh-tw*/ = 1028,
	Croatian /*hr*/ = 1050,
	Czech /*cs*/ = 1029,
	Danish /*da*/ = 1030,
	Dutch_TheNetherlands /*nl-nl*/ = 1043,
	Dutch_Belgium /*nl-be*/ = 2067,
	English_Australia /*en-au*/ = 3081,
	English_Belize /*en-bz*/ = 10249,
	English_Canada /*en-ca*/ = 4105,
	English_Caribbean /*en-cb*/ = 9225,
	English_Ireland /*en-ie*/ = 6153,
	English_Jamaica /*en-jm*/ = 8201,
	English_NewZealand /*en-nz*/ = 5129,
	English_Phillippines /*en-ph*/ = 13321,
	English_SouthAfrica /*en-za*/ = 7177,
	English_Trinidad /*en-tt*/ = 11273,
	English_UnitedKingdom /*en-gb*/ = 2057,
	English_UnitedStates /*en-us*/ = 1033,
	Estonian /*et*/ = 1061,
	Farsi /*fa*/ = 1065,
	Finnish /*fi*/ = 1035,
	Faroese /*fo*/ = 1080,
	French_France /*fr-fr*/ = 1036,
	French_Belgium /*fr-be*/ = 2060,
	French_Canada /*fr-ca*/ = 3084,
	French_Luxembourg /*fr-lu*/ = 5132,
	French_Switzerland /*fr-ch*/ = 4108,
	Gaelic_Ireland /*gd-ie*/ = 2108,
	Gaelic_Scotland /*gd*/ = 1084,
	German_Germany /*de-de*/ = 1031,
	German_Austria /*de-at*/ = 3079,
	German_Liechtenstein /*de-li*/ = 5127,
	German_Luxembourg /*de-lu*/ = 4103,
	German_Switzerland /*de-ch*/ = 2055,
	Greek /*el*/ = 1032,
	Hebrew /*he*/ = 1037,
	Hindi /*hi*/ = 1081,
	Hungarian /*hu*/ = 1038,
	Icelandic /*is*/ = 1039,
	Indonesian /*id*/ = 1057,
	Italian_Italy /*it-it*/ = 1040,
	Italian_Switzerland /*it-ch*/ = 2064,
	Japanese /*ja*/ = 1041,
	Korean /*ko*/ = 1042,
	Latvian /*lv*/ = 1062,
	Lithuanian /*lt*/ = 1063,
	FYRO_Macedonian /*mk*/ = 1071,
	Malay_Malaysia /*ms-my*/ = 1086,
	Malay_Brunei /*ms-bn*/ = 2110,
	Maltese /*mt*/ = 1082,
	Marathi /*mr*/ = 1102,
	Norwegian_Bokmal /*no-no*/ = 1044,
	Norwegian_Nynorsk /*no-no*/ = 2068,
	Polish /*pl*/ = 1045,
	Portuguese_Portugal /*pt-pt*/ = 2070,
	Portuguese_Brazil /*pt-br*/ = 1046,
	Raeto_Romance /*rm*/ = 1047,
	Romanian_Romania /*ro*/ = 1048,
	Romanian_RepublicOfMoldova /*ro-mo*/ = 2072,
	Russian /*ru*/ = 1049,
	Russian_RepublicOfMoldova /*ru-mo*/ = 2073,
	Sanskrit /*sa*/ = 1103,
	Serbian_Cyrillic /*sr-sp*/ = 3098,
	Serbian_Latin /*sr-sp*/ = 2074,
	Setsuana /*tn*/ = 1074,
	Slovenian /*sl*/ = 1060,
	Slovak /*sk*/ = 1051,
	Sorbian /*sb*/ = 1070,
	Spanish_Spain /*es-es*/ = 1034,
	Spanish_Argentina /*es-ar*/ = 11274,
	Spanish_Bolivia /*es-bo*/ = 16394,
	Spanish_Chile /*es-cl*/ = 13322,
	Spanish_Colombia /*es-co*/ = 9226,
	Spanish_CostaRica /*es-cr*/ = 5130,
	Spanish_DominicanRepublic /*es-do*/ = 7178,
	Spanish_Ecuador /*es-ec*/ = 12298,
	Spanish_Guatemala /*es-gt*/ = 4106,
	Spanish_Honduras /*es-hn*/ = 18442,
	Spanish_Mexico /*es-mx*/ = 2058,
	Spanish_Nicaragua /*es-ni*/ = 19466,
	Spanish_Panama /*es-pa*/ = 6154,
	Spanish_Peru /*es-pe*/ = 10250,
	Spanish_PuertoRico /*es-pr*/ = 20490,
	Spanish_Paraguay /*es-py*/ = 15370,
	Spanish_ElSalvador /*es-sv*/ = 17418,
	Spanish_Uruguay /*es-uy*/ = 14346,
	Spanish_Venezuela /*es-ve*/ = 8202,
	Sutu /*sx*/ = 1072,
	Swahili /*sw*/ = 1089,
	Swedish_Sweden /*sv-se*/ = 1053,
	Swedish_Finland /*sv-fi*/ = 2077,
	Tamil /*ta*/ = 1097,
	Tatar /*tt*/ = 1092,
	Thai /*th*/ = 1054,
	Turkish /*tr*/ = 1055,
	Tsonga /*ts*/ = 1073,
	Ukrainian /*uk*/ = 1058,
	Urdu /*ur*/ = 1056,
	Uzbek_Cyrillic /*uz-uz*/ = 2115,
	Uzbek_Latin /*uz-uz*/ = 1091,
	Vietnamese /*vi*/ = 1066,
	Xhosa /*xh*/ = 1076,
	Yiddish /*yi*/ = 1085,
	Zulu /*zu*/ = 1077
}

public enum SPSiteTemplates
{
	TeamSite,
	BlankSite,
	DocumentWorkspace,
	BasicMeetingWorkspace,
	BlankMeetingWorkspace,
	DecisionMeetingWorkspace,
	SocialMeetingWorkspace,
	MultiPageMeetingWorkspace,
	Wiki,
	Blog,
	/// <summary>
	/// A central document management location for an enterprise
	/// </summary>
	DocumentCenter,
	/// <summary>
	/// A central location in which records managers can define routes for incoming files
	/// </summary>
	RecordsCenter1,
	RecordsCenter2,
	PublishingSite,
	/// <summary>
	/// A site for publishing web pages on a schedule with workflow features enabled
	/// </summary>
	PublishingSite2,
	PressReleasesSite,
	/// <summary>
	/// A publishing site for web pages using approval workflows
	/// </summary>
	PublishingSiteWithWorkflow,
	/// <summary>
	/// A site for publishing news and articles
	/// </summary>
	NewsSite,
	/// <summary>
	/// A site for creating, managing, and delivering web pages, dashboards, and Key Performance Indicators (KPIs)
	/// </summary>
	ReportCenter,
	/// <summary>
	/// A starter hierarchy for an intranet divisional portal
	/// </summary>
	SPSPortal,
	/// <summary>
	/// A profile site that includes page layouts with zones
	/// </summary>
	ProfileSite,
	/// <summary>
	/// A site collection preconfigured for revision-controlled, secure content creation and publication
	/// </summary>
	PublishingPortal,
	/// <summary>
	/// Keep in mind that only one of these can be provisioned per Shared Services Provider
	/// </summary>
	MySiteHost,
	/// <summary>
	/// A site designed to deliver the search query and results experience
	/// </summary>
	SearchCenter,
	/// <summary>
	/// A superset of the previous; does not appear in navigation bars
	/// </summary>
	SearchCenter2
}

Un poco de magia enum

Lo que nos queda es:

  • Convertir enums SPLocales a su representación correspondiente en uint
  • Convertir enums SPSiteTemplates a su representación en cadena correspondientes
  • Evitar intentar crear sitios duplicados

Para convertir enums SPLocales a su representación uint, sólo tenemos que hacer el casting correspondiente. Esta es la razón por la que hicimos al enum heredar de unit y setear el valor apropiado en cada elemento.

Para poder convertir enums SPSiteTemplates a cadenas, el método común es utilizar atributos y reflexión, pero personalmente no me gusta esa forma, así que hice una con diccionarios y métodos de extensión. La idea es proveer al enum la posibilidad de convertirse en una cadena, y estas cadenas de ser parseadas en el enum nuevamente. Para tener este tipo de relación en ambos sentidos, usé la implementación de BiDictionaryOneToOne del usuario de StackOverflow’s Joel in Go, en una pregunta de sobre cómo hacer un diccionario buscable key-value y buscable value-key: Bidirectional 1 to 1 Dictionary in C#.

public static class SPSiteTemplateMethodExtension
{
	private const string SP_SITE_TEMPLATE_TEAM_SITE = "STS#0";
	private const string SP_SITE_TEMPLATE_BLANK_SITE = "STS#1";
	private const string SP_SITE_TEMPLATE_DOCUMENT_WORKSPACE = "STS#2";
	private const string SP_SITE_TEMPLATE_BASIC_MEETING_WORKSPACE = "MPS#0";
	private const string SP_SITE_TEMPLATE_BLANK_MEETING_WORKSPACE = "MPS#1";
	private const string SP_SITE_TEMPLATE_DECISION_MEETING_WORKSPACE = "MPS#2";
	private const string SP_SITE_TEMPLATE_SOCIAL_MEETING_WORKSPACE = "MPS#3";
	private const string SP_SITE_TEMPLATE_MULTI_PAGE_MEETING_WORKSPACE = "MPS#4";
	private const string SP_SITE_TEMPLATE_WIKI = "WIKI#0";
	private const string SP_SITE_TEMPLATE_BLOG = "BLOG#0";
	private const string SP_SITE_TEMPLATE_DOCUMENT_CENTER = "BDR#0";
	private const string SP_SITE_TEMPLATE_RECORDS_CENTER_1 = "OFFILE#0";
	private const string SP_SITE_TEMPLATE_RECORDS_CENTER_2 = "OFFILE#1";
	private const string SP_SITE_TEMPLATE_PUBLISHING_SITE = "CMSPUBLISHING#0";
	private const string SP_SITE_TEMPLATE_PUBLISHING_SITE_2 = "BLANKINTERNET#0";
	private const string SP_SITE_TEMPLATE_PRESS_RELEASES_SITE = "BLANKINTERNET#1";
	private const string SP_SITE_TEMPLATE_PUBLISHING_SITE_WITH_WORKFLOW = "BLANKINTERNET#2";
	private const string SP_SITE_TEMPLATE_NEWS_SITE = "SPSNHOME#0";
	private const string SP_SITE_TEMPLATE_REPORT_CENTER = "SPSREPORTCENTER#0";
	private const string SP_SITE_TEMPLATE_SPS_PORTAL = "SPSPORTAL#0";
	private const string SP_SITE_TEMPLATE_PROFILE_SITE = "PROFILES#0";
	private const string SP_SITE_TEMPLATE_PUBLISHING_PORTAL = "BLANKINTERNETCONTAINER#0";
	private const string SP_SITE_TEMPLATE_MY_SITE_HOST = "SPSMYSITEHOST#0";
	private const string SP_SITE_TEMPLATE_SEARCH_CENTER = "SRCHCENTERLITE#0";
	private const string SP_SITE_TEMPLATE_SEARCH_CENTER_2 = "SRCHCENTERLITE#1";

	private static BiDictionaryOneToOne<SPSiteTemplates, string> _templates;
	private static BiDictionaryOneToOne<SPSiteTemplates, string> Templates {
		get {
			if (_templates == null) {
				_templates = new BiDictionaryOneToOne<SPSiteTemplates, string>();
				_templates.Add(SPSiteTemplates.BasicMeetingWorkspace, SP_SITE_TEMPLATE_BASIC_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.BlankMeetingWorkspace, SP_SITE_TEMPLATE_BLANK_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.BlankSite , SP_SITE_TEMPLATE_BLANK_SITE);
				_templates.Add(SPSiteTemplates.Blog , SP_SITE_TEMPLATE_BLOG);
				_templates.Add(SPSiteTemplates.DecisionMeetingWorkspace , SP_SITE_TEMPLATE_DECISION_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.DocumentCenter , SP_SITE_TEMPLATE_DOCUMENT_CENTER);
				_templates.Add(SPSiteTemplates.DocumentWorkspace , SP_SITE_TEMPLATE_DOCUMENT_WORKSPACE);
				_templates.Add(SPSiteTemplates.MultiPageMeetingWorkspace , SP_SITE_TEMPLATE_MULTI_PAGE_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.MySiteHost , SP_SITE_TEMPLATE_MY_SITE_HOST);
				_templates.Add(SPSiteTemplates.NewsSite , SP_SITE_TEMPLATE_NEWS_SITE);
				_templates.Add(SPSiteTemplates.PressReleasesSite , SP_SITE_TEMPLATE_PRESS_RELEASES_SITE);
				_templates.Add(SPSiteTemplates.ProfileSite , SP_SITE_TEMPLATE_PROFILE_SITE);
				_templates.Add(SPSiteTemplates.PublishingPortal , SP_SITE_TEMPLATE_PUBLISHING_PORTAL);
				_templates.Add(SPSiteTemplates.PublishingSite , SP_SITE_TEMPLATE_PUBLISHING_SITE);
				_templates.Add(SPSiteTemplates.PublishingSite2 , SP_SITE_TEMPLATE_PUBLISHING_SITE_2);
				_templates.Add(SPSiteTemplates.PublishingSiteWithWorkflow , SP_SITE_TEMPLATE_PUBLISHING_SITE_WITH_WORKFLOW);
				_templates.Add(SPSiteTemplates.RecordsCenter1 , SP_SITE_TEMPLATE_RECORDS_CENTER_1);
				_templates.Add(SPSiteTemplates.RecordsCenter2 , SP_SITE_TEMPLATE_RECORDS_CENTER_2);
				_templates.Add(SPSiteTemplates.ReportCenter , SP_SITE_TEMPLATE_REPORT_CENTER);
				_templates.Add(SPSiteTemplates.SearchCenter , SP_SITE_TEMPLATE_SEARCH_CENTER);
				_templates.Add(SPSiteTemplates.SearchCenter2 , SP_SITE_TEMPLATE_SEARCH_CENTER_2);
				_templates.Add(SPSiteTemplates.SocialMeetingWorkspace , SP_SITE_TEMPLATE_SOCIAL_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.SPSPortal , SP_SITE_TEMPLATE_SPS_PORTAL);
				_templates.Add(SPSiteTemplates.TeamSite , SP_SITE_TEMPLATE_TEAM_SITE);
				_templates.Add(SPSiteTemplates.Wiki , SP_SITE_TEMPLATE_WIKI);
			}
			return _templates;
		}
	}

	public static string GetSharePointStringKey(this SPSiteTemplates siteTemplate)
	{
		return Templates.GetByFirst(siteTemplate);
	}
}

Sí, eso de ahí es un singleton y podría tener problemas potenciales de concurrencia, pero no estamos esperando agregar/remover/editar items. Además, yo habría ido con un constructor estático, pero los métodos de extensión tienen que implementarse en clases estáticas, y las clases marcadas como estáticas no tienen permitido tener constructores estáticos.

Toques extras

Con esto ya casi había terminado, pero resulta que si queremos verificar que un sitio existe, necesitamos usar su URL completa, pero si usamos el símbolo de los dos puntos ( : ) entonces vamos a tener una linda SPException diciéndonos que la URL es inválida. Lo que tenemos que hacer es usar URLs relativas a la URL del site collection, así que incluí un par de líneas para parsearlas.

Así que, aquí está la última versión del método CreateSite:

public SPWeb CreateSite(string parentSiteURL, string siteURLRequested, string siteTitle, SPSiteTemplates siteTemplate, SPLocales locale, string description)
{
	SPWeb resultWeb = null;

	PerformActionOnSite(parentSiteURL, (siteCollection, parentWeb) =>
	{
		SPWebTemplateCollection Templates = siteCollection.GetWebTemplates(Convert.ToUInt32(locale));
		SPWebTemplate spTemplate = Templates[siteTemplate.GetSharePointStringKey()];

		var rootUrl = siteCollection.Url;
		if (siteURLRequested.ToUpper().StartsWith(rootUrl.ToUpper()))
		{
			siteURLRequested = siteURLRequested.Remove(0, rootUrl.Length);
			if (siteURLRequested.StartsWith("/"))
				siteURLRequested = siteURLRequested.Remove(0, 1);

			if (siteURLRequested.Length == 0) //they were equal, don't create the root site
			{
				resultWeb = parentWeb;
			}
			else if (parentWeb.Webs[siteURLRequested].Exists) //it's not the root site, but it exist anyway
			{
				resultWeb = parentWeb.Webs[siteURLRequested];
			}
			else
			{
				resultWeb = parentWeb.Webs.Add(siteURLRequested, siteTitle, description, Convert.ToUInt32(locale), spTemplate, false, false);
			}
		}
	});

	return resultWeb;
}

Si se sienten confundidos por el método PerformActionOnSite, es un wrapper que creé para automáticamente usar y hacer dispose en los objetos SPSite y SPWeb .

protected void PerformActionOnSite(string siteUrl, Action<SPSite, SPWeb> action)
{
	using (SPSite site = new SPSite(siteUrl))
	{
		using (SPWeb web = site.OpenWeb())
		{
			action(site, web);
			web.Close();
		}
		site.Close();
	}
}

Ok, entonces ya todo está en su lugar, deberían poder usarlo así::

CreateSite("http://siteCollectionUrl", "http://siteCollectionUrl/newSite", SPSiteTemplates.PublishingSite, SPLocales.English_UnitedStates, "site description");

Eso es todo. Espero que esto sea útil para ustedes. Subí una copia de todo este código en http://files.alphasmanifesto.com/Software/

¡Disfruten!

Referencias:

Link del día: AngularJS

Hace poquito apareció en mi feed de Youtube una charla de Google Tech Talks llamada How to Write Clean and Testable Code. Para ser sinceros, el video dura más de una hora así que no lo ví, pero en lugar de eso busqué las diapositivas que se habían usado en él y las encontré aquí: How to write Clean and Testable Code Slides. Las diapositivas no me resultaron demasiado reveladoras tampoco pero sí resaltan algunos conceptos claves que es bueno siempre tener en mente.

Más allá de eso, en las diapositivas (y muy seguramente en la charla) se menciona a AngularJS, así que lo fui a buscar. Parece que AngularJS es un sistema de templating a través de JavaScript, pero mucho mayor que simplemente templating. Digamos que más que estar orientado en generar HTML a partir de datos, también se preocupa de la forma en que esos datos deben interactuar, de forma que, podríamos decir, también genera algo de código dinámicamente para que estos datos funcionen correctamente.

No lo he probado, pero ellos dicen que es la forma en la que debería haber sido HTML si es que hubiera sido pensado desde un principio para aplicaciones web.

¿Alguien lo probó? ¿Cuáles son sus experiencias?

Soy un zorrinito javascript.

Link del día: Proyectos ágiles de verdad

No sé si alguno de ustedes está subscripto al Dev Channel de Google Chrome, en donde pueden obtener features nuevas más rápidamente, por supuesto, sabiendo que no están del todo probadas. Si están ahí, sabrán que la cantidad de cambios y updates es realmente mucha, y que a pesar de todo eso, el proyecto sigue creciendo y trabajando en el ambiente, uno se pregunta:

¿Cómo hacen para mantener ordenado un proceso tan dinámico?

Esa pregunta me fue respondida por un link provisto por @Woork, quién nos otorga una presentación sobre el Ciclo de Vida del proyecto Chrome, presentado por parte de Anthony LaForge, llamado Chrome Release Cycle. En esta presentación podemos ver los distintos problemas a los que se enfrentaron al momento de manejar tiempos y los distintos problemas de la programación común, y cómo fueron adaptando la estrategia para lograr un punto más dinámico y accesible.

Quienes estemos subscriptos a esa rama de desarrollo sabemos ahora que funciona, y que da resultados visibles.

Soy un zorrinito apurado.

Link del día: Bases de datos en las nubes

Está de moda últimamente hablar de la computación en la nube (y no es para menos, ya que realmente propone un cambio interesante en la forma en que se mantiene el software).  Lo más común para esto son servicios en donde podemos alojar nuestros sistemas con más o menos control de la plataforma, pero pocos de ellos se han centrado solamente en los datos.

Database.com es un sistema online que nos permitirá crear una cuenta para comenzar a utilizar bases de datos on the cloud. Lo mejor de todo esto, es que para comenzar, siempre y cuando no exceda cierta cantidad, será gratuito. ¿Qué mejor que eso? (Que sea todo gratuito, lo sé, pero hey, alguien tiene que mantenerlo.) Cabe aclarar que con nuestra cuenta tendremos también la replicación automática (por supuesto, estamos en la nube), backups, tuning según el uso, upgrades del sistema, y generación de los ambientes de desarrollo, test y training.

Sepan también que esta gente es la misma que desarrolló SalesForce,  de forma que no se trata de ninguna tontería y sí estamos hablando de algo serio y confiable.

¡Gracias Kabytes por la información!

Soy un zorrinito en la nube.

Link del día: Bytecode for Dummies

Para aquellos que desarrollamos sobre lo que se llaman plataformas (como por ejemplo .NET o Java), sabemos que el código que nosotros escribimos no se compila a lenguaje de máquina realmente, sino que se compila en algún lenguaje intermedio que luego es interpretado para una mejor ejecución en la máquina apropiada sobre la que esté corriendo la plataforma.

El punto que muchos dejamos de lado es saber interpretar ese lenguaje intermedio. Este lenguaje muchas veces puede proveernos información muy válida sobre problemas de performance que puede sufrir nuestra aplicación, usos de memoria no liberados, o incluso de la forma en la que se realizan llamadas al sistema operativo.

Charles Nutter realizó una presentación llamada JVM Bytecode for Dummies (and for the rest of you all) que explica detalladamente cómo podemos iniciarnos en este mundo. Él se enfocó en el bytecode de la máquina virtual de Java, pero esto es aplicable a otras máquinas virtuales y a otras plataformas también. Puede que al principio nos maree un poco con ejemplos algo complejos, pero luego la teoría va tomando color hasta ser bastante tangible y podemos entender cómo el bytecode realmente refleja nuestro código. Mejor aún, podemos directamente programar con bytecode y aprovecharnos de eso mismo.

Soy un zorrinito interpretado.