Alpha's Manifesto

A black and white figure's thought-hive

Updates what-now: deployments automáticos en Heroku

La travesía y el resultado final

Desde hace un tiempo estoy intentando lograr que mi aplicación what-now se instale automáticamente en Heroku ante cambios realizado en su código. Este artículo cuenta sobre la travesía de descubrir por qué no me resultó tan simple como uno esperaría, y cómo terminó el proceso final en una manera que me resultó aceptable. Creo que es un buen resultado para reutilizar en otros proyectos, y tanto mejor que algunas recomendaciones que he visto por ahí.

(Read more →)

Consejos para un no-programador

O "cómo no perderse en este bosque"

Una amiga mía me comentó que quería comenzar a programar, y de alguna forma lo entendí implícitamente como un buen momento para dar consejos de orientación. No es por sonar arrogante, pero sabemos que la programación puede ser una tarea desafiante, y me pregunté cuáles serían los mejores consejos para alguien que está empezando.

(Read more →)

Las características no tan conocidas de los batch files

Unleash the power of .bat!

Batch File

Para los que trabajamos en Windows, es bastante común que en algún momento u otro terminemos automatizando alguna tarea a través de batch files. Por supuesto, aquellos que también trabajamos con Windows desde hace tiempo (incluso desde los tiempos de DOS) seguramente no les tengamos mucha confianza por que estaban bastante limitados.

Por suerte para nosotros, ese ya no es el caso, y los batch files contienen una buena cantidad de habilidades que los hacen una herramienta muy valiosa. Personalmente opino que no llegan al nivel de un shell script, pero aún así nos permiten hacer muchas cosas.

Para una buena mirada a varias características que por lo general no obtienen la atención merecida, miremos el post de StackOverflow Hidden features of Windows Batch Files, que de seguro nos dará una nueva visión de lo que podemos y no podemos hacer.

Soy un zorrinito batch.

on{X}

Automatizando nuestra vida, un teléfono a la vez

De parte de MicroSiervos me entero que Microsoft está desarrollando un sistema de macros basadas en eventos para teléfonos móbiles. El proyecto se llama on{X} y aquellos que conozcan IfTTT verán una gran similaridad.

El sistema se basa en eventos porque los macros se disparan según determinadas situaciones se presenten. Ver las recetas pre-armadas ya les dará una idea:

  • Lanzar la aplicación de música cuando estoy caminando.
  • Cuando llegue a casa, recuérdame comprar leche.
  • Recuérdame ir al gimnasio si no he ido por 3 días.
  • Enviar el mensaje “voy para allá” a mi esposa cuando salgo del trabajo.
  • Cuando mi novio me pregunta “dónde?”, responder automáticamente con mi localización.

Como esas, se pueden hacer muchas, ya que el código detrás de ella es simple JavaScript, lo que permitirá que una gran comunidad comience a construir sobre ellas. En este momento solo funciona para teléfonos Android (Microsoft, really?) pero esperemos que pronto se extienda el soporte. Todavía la comunidad en los foros y el blog están algo vacíos, pero nuevamente, es algo muy reciente todavía.

Yo realmente pienso que esta idea puede despegar.

Soy un zorrinito automatizado.

HtmlUnit en .NET

Unit testing para web frontends

Hay un artículo en particular de Steve Sanderson llamado Using HtmlUnit on .NET Headless browser automation que indaga sobre los beneficios de utilizar esa librería para simular un browser completamente funcional que podemos utilizar para nuestro unit testing, y, por qué no, para automatizar tareas en un browser. La idea principal es poder ejecutar tareas como si de un browser se tratara, e inspeccionar los elementos de la página e interactuar con ellos.

Cabe destacar que HtmlUnit en realidad está hecho para Java, pero puede portarse a .NET de la forma que Steve Anderson menciona. He leído por ahí que es un poco lento, pero creo que su API agrega la suficiente simpleza como para trabajar fácilmente por él.

Soy un zorrinito automatizado.

Udacity

Otra iniciativa más de enseñanza online

Me encontré como noticia en ALT 1040 que Sebastian Thrun dejó Stanford para comenzar su propia iniciativa de enseñanza online, llamada Udacity. En este preciso momento se están enseñando sólo dos cursos: Cómo construir un auto que se maneja solo y Cómo construir un motor de búsqueda. Ambos están dados por Thrun y el del motor de búsqueda está también asociado con David Evans.

Se vienen nuevos cursos durante este año, que también parecen ser muy interesantes, como Sistemas Operativos, Sistemas Distribuidos, Redes, Seguridad, Algoritmos y estructura de datos, Prácticas de Ingeniería de Software, Construir aplicaciones web, etc. Nombré los cursos que están nombrados en la página al día de hoy, pero quién sabe qué traerá el futuro. Parece que por hoy está teniendo mucho éxito.

Soy un zorrinito estudioso.

Link del día: If This Then That

IfTTT.com es un servicio que me atrevo a calificar de “maravilloso” en donde nos permiten integrar distintas redes sociales o servicios 2.0 en workflows que nosotros definiremos. Dependiendo del uso que le demos, puede sernos más o menos útiles.

Aunque su página de WTF lo explica, les daré un ejemplo de lo que se puede hacer para que lo vean de una forma simple, con unas reglas que uso yo:

  • Cuando alguien me taggea en una foto en Facebook, que me envíe una copia de esa foto a mi email
  • Cuando favoriteo un tweet, que me agregue ese tweet a Instapaper (yo uso los favoritos de Twitter para cosas que quiero leer después)
  • Cuando le pongo una estrella a una entrada de Google Reader, que me la copie a Instapaper (yo uso las estrellas de GReader para cosas que quiero leer después)
  • Cuando agrego un post a este blog, que lo twitee automáticamente

Y este servicio todavía está en Beta, me encantaría que agregaran en el futuro nuevos servicios y nuevas integraciones. La idea está fantástica y su utilidad es impresionante. Por supuesto, también podemos hacer interactuar muchos otros servicios (Craiglist, Evernote, Youtube, feeds, Facebook, Twitter, teléfono y sms!, linked.in, vimeo…).

Como extra, podemos utilizar “recetas” (tareas que ya otra gente ha programado) o crear las nuestras propias.

¿Para qué lo utilizarían ustedes?

Soy un zorrinito automatizado.

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:

[sourcecode language=”csharp”]
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;
}
[/sourcecode]

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):

[sourcecode language=”csharp”]
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
}
[/sourcecode]

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#.

[sourcecode language=”csharp”]
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);
}
}
[/sourcecode]

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:

[sourcecode language=”csharp”]
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;
}
[/sourcecode]

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 .

[sourcecode language=”csharp”]
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();
}
}
[/sourcecode]

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

[sourcecode language=”csharp”]
CreateSite("http://siteCollectionUrl", "http://siteCollectionUrl/newSite", SPSiteTemplates.PublishingSite, SPLocales.English_UnitedStates, "site description");
[/sourcecode]

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: MasterBranch, CVs automáticos

Hace muy poco recibí un correo muy curioso de la gente de MasterBranch.

Este es el nombre de una aplicación que apenas está saliendo al público cuya idea es muy interesante: más allá de ser una red social de trabajo como cualquier otra, ellos escanean la participación del usuario (es decir, nuestra) en repositorios de código públicos, como Google Code, o SourceForge, GitHub, etc. Con esa información, automáticamente actualizan la información de nuestro trabajo según las tecnologías con las que trabajemos, según las aplicaciones que utilicemos.

La magia de eso es que todo es automatizado, una vez que esté funcionando del todo correctamente y que tengamos nuestro perfil bien configurado, sólo tenemos que trabajar para que nuestro perfil como desarrolladores se actualice según con lo que hagamos.

¿Creen que esta idea se podría llevar a más niveles? Por ejemplo, ¿lo integrarían con otros sistemas para plantear la experiencia del usuario en otras áreas, como documentos, marketing, facturación, ventas, diseño?

Soy un zorrinito automático.

Link del día: Backups de Facebook

Me encantaría poder encontrar alguna herramienta que haga un backup local de algún servicio web como Facebook. Sé que existen herramientas distintas para distintos servicios, y algunas son más fáciles de usar que otras.

Por lo pronto, creo que nos concentraremos en ciertos tipos de datos de ciertos servicios. En este caso, las fotos de Facebook, tanto las nuestras como las de nuestros amigos. Hay una herramienta que nos permite descargar todo eso, llamada PhotoGrabber. PhotoGrabber es una herramienta disponible para Windows y para Mac OS X (aunque también hay directivas para usarlo en Linux, y no parece nada difícil) que se conecta a nuestra cuenta de Facebook y baja una gran mayoría de nuestras fotos y de nuestros amigos.

Debo de decir que se trata de una gran mayoría de fotos y no todas porque Facebook de alguna forma restringe según las normas de privacidad el acceso que las aplicaciones pueden tener a las fotos (y a otros datos de los usuarios), y por eso puede que en nuestro caso no baje todas las imágenes. Siendo el caso, podemos revisar en detalle qué es lo que ocurre en el FAQ de PhotoGrabber.

No lo he probado personalmente, pero sabiendo que tiene pocos bugs y una buena respuesta de parte de la comunidad, veo que ha logrado funcionar y no ha decepcionado a los usuarios. ¿Alguien que quisiera dar un detalle de cómo es?

Soy un zorrinito facebooker.