[MOSS] Llamadas a métodos .NET desde un Xsl (ContentByQueryWebPart)

En ocasiones puede ser necesario que desde un Xsl tengamos que llamar a un método .NET. En mi caso era para parsear el valor de un campo que contiene un Xml.

Lo primero será crearnos una clase para definir los métodos que vamos a llamar desde nuestros Xsl:

public class XsltCustomFunctions
{
    #region Variables
 
    /// <summary>
    /// Singleton Instance
    /// </summary>
    static readonly XsltCustomFunctions instance = new XsltCustomFunctions();
 
    #endregion
 
    #region Properties
 
    /// <summary>
    /// Gets the instance.
    /// </summary>
    /// <value>The instance.</value>
    public static XsltCustomFunctions Instance
    {
        get
        {
            return instance;
        }
    }
 
    #endregion
 
    #region Constructor
 
    /// <summary>
    /// Initializes the <see cref="XsltCustomFunctions"/> class.
    /// </summary>
    static XsltCustomFunctions()
    {
 
    }
 
    /// <summary>
    /// Initializes a new instance of the <see cref="XsltCustomFunctions"/> class.
    /// </summary>
    XsltCustomFunctions()
    {
 
    }
 
    #endregion
 
    #region Methods
 
    #region public
 
    /// <summary>
    /// Gets the average.
    /// </summary>
    /// <param name="xml">The XML.</param>
    /// <returns></returns>
    public string GetAverage(string xml)
    {
        string response = String.Empty;
 
        if (!String.IsNullOrEmpty(xml))
        {
            ///
        }
 
        return response;
    }
 
    #endregion
 
    #endregion
}

Lo siguiente que tenemos que hacer es añadir un ExtensionObject para poder llamar a los métodos de la clase que creamos anteriormente. Esto lo vamos a hacer sobreescribiendo el método ModifyXsltArgumentList del ContentQueryByWebPart:

public class MyContentByQueryWebPart : ContentByQueryWebPart
{
    /// <summary>
    /// Modifies the XSLT argument list.
    /// </summary>
    /// <param name="argList">The arg list.</param>
    protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
    {
        argList.AddExtensionObject("http://www.midominio.es/Assembly/XsltCustomFunctions", 
            TC.REDILC.Common.XsltCustomFunctions.Instance);
 
        base.ModifyXsltArgumentList(argList);
    }
}

El primer parámetro es el namespace (Puedes poner el que tu quieras en mi caso uso el nombre de la empresa + nombre de assembly + clase) que luego usaremos en el Xsl y el segundo es una instancia de la clase que creamos anteriormente.

Como veis, se hace uso del patrón Singleton para no estar creando un monton de instancias cada vez que se llama al método y así mejorar el performance y ahorrar memoria.

Por último solo nos queda crear el Xsl y llamar a nuestro método. Lo primero registrar el namespace que usamos cuando añadimos el ExtensionObject:

<xsl:stylesheet 
  version="1.0" 
  exclude-result-prefixes="x d xsl msxsl cmswrt"
  xmlns:x="http://www.w3.org/2001/XMLSchema" 
  xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" 
  xmlns:cmswrt="http://schemas.microsoft.com/WebParts/v3/Publishing/runtime"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:geeksms="http://www.midominio.es/Assembly/XsltCustomFunctions">

Y por último la llamada al método:

<div class="average">
    <xsl:value-of select="geeksms:GetAverage(Xml)" />
</div>
Espero que os sirva, un saludo y buen finde para todos :)
Publicado por Luis Ruiz Pavón | con no comments

[MOSS 2007] Conectar WebParts programáticamente

Conectar 2 WebParts (por ejemplo un buscador y grid) a través del interfaz de SharePoint es una cosa sencilla, pero seguro que alguna vez has necesitado hacerlo a través del modelo de objetos de MOSS. En nuestro caso tenemos varios Custom WebParts que una vez desplegado el portal necesitamos conectarlos y lo hemos hecho vía Feature (No voy a poner el código entero del FeatureReceiver).

Uno de los problemas que te puedes encontrar al hacer esto lo comenté en un post anterior:

http://geeks.ms/blogs/lruiz/archive/2009/11/18/moss-2007-webparts-del-tipo-errorwebpart-aparecen-cuando-usamos-splimitedwebpartmanager.aspx

y en nuestro caso son buscadores sobre unos custom CQWP, por eso el Receiver al no disponer de contexto debe contener estas líneas:

if (HttpContext.Current == null)
{
    HttpRequest httpRequest = new HttpRequest(String.Empty, web.Url, String.Empty);
    HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new StringWriter()));
    HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
}

Una vez hecho esto, a través de las propiedades de la feature, obtenemos la página y los 2 WebPart que queremos conectar.

Localizamos la página en la colección de páginas, obtenemos una referencia al objeto SPFile (Para poder hacer CheckOut, CheckIn y Publish).

Obtenemos una referencia al SPLimitedWebPartManager de la página para poder obtener la colección de WebParts de la misma mediante la propiedad WebParts.

Una vez que hemos localizado los 2 WebParts que vamos a conectar, obtenemos el ConsumerConnectionPointCollection (Contiene una colecciónde ConsumerConnectionPoint que habilita a un WebPart a actuar como Provider) y el ProviderConnectionPointCollection (Contiene una colección de ProviderConnectionPoint que habilita a un WebPart a actuar como Provider).

Una vez hecho esto necesitamos un objeto del tipo SPWebPartConnection que lo obtenemos mediante el método SPConnectWebParts al que le pasamos como parámetros el WebPart que suministra la conexión y su ProviderConnectionPoint y el WebPart que va a consumir su conexión y su ConsumerConnectionPoint.

Por último añadimos a la colección de conexiones del WebPartManager la conexión que hemos obtenido anteriormente y ya tendremos nuestros WebPart conectados vía modelo de objetos.

Aquí está el código de ejemplo:

PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
PublishingPageCollection pages = publishingWeb.GetPublishingPages();
 
/// Check if the Page exists, null if not exists
PublishingPage page = pages.SingleOrDefault(p => p.Name == pageName);
 
if (page != null)
{
    SPWebparts.WebPart wpProvider = null;
    SPWebparts.WebPart wpConsumer = null;
 
    /// Get SPFile because it's necessary for Publish and Approve(This one if
    /// PagesList.EnableModeration is true)
    SPFile aspx = web.GetFile(page.Url.ToString());
 
    aspx.CheckOut();
 
    SPLimitedWebPartManager webPartManager =
        aspx.GetLimitedWebPartManager(PersonalizationScope.Shared);
 
    SPLimitedWebPartCollection webparts =
        webPartManager.WebParts;
 
    wpProvider = (SPWebparts.WebPart)webPartManager.WebParts[webpart.ID];
    wpConsumer = (SPWebparts.WebPart)webPartManager.WebParts[webpart.ID];
 
    /// Creates the connection
    if (wpProvider != null &&
        wpConsumer != null)
    {
        ConsumerConnectionPointCollection consumerConnection
            = webPartManager.GetConsumerConnectionPoints(wpConsumer);
 
        ProviderConnectionPointCollection providerConnection
            = webPartManager.GetProviderConnectionPoints(wpProvider);
 
        SPWebPartConnection webPartConnection =
            webPartManager.SPConnectWebParts(wpProvider,
                                             providerConnection[0],
                                             wpConsumer,
                                             consumerConnection[0]);
 
        webPartManager.SPWebPartConnections.Add(webPartConnection);
    }
 
    aspx.CheckIn(String.Empty);
    aspx.Publish(String.Empty);
 
    if (publishingWeb.PagesList.EnableModeration)
    {
        aspx.Approve(String.Empty);
    }
}

Un saludo

[Tips C#] Mi opinión sobre el artículo “IsDate() function in C#”

Esta máñana leyendo mis RSS, he visto este artículo y he pensado en comentarlo, pero mejor me hago un post y muestro como lo haría yo.

Viendo el primer código que publicó:

image 

Salta a la vista que con un TryParse se quita la excepción y además recordar que se introdujo en el 2.0 precisamente para evitar usar el try catch y mejorar el performance, es decir que una vez modificado le queda:

image

Pero para mí ese método sería candidato a un Extension Method de la clase DateTime:

public static class Extensions
{
    /// <summary>
    /// Determines whether the specified datetime is date.
    /// </summary>
    /// <param name="datetime">The datetime.</param>
    /// <param name="inputdate">The inputdate.</param>
    /// <returns>
    ///     <c>true</c> if the specified datetime is date; otherwise, <c>false</c>.
    /// </returns>
    public static bool IsDate(this DateTime datetime, string inputdate)
    {
        return DateTime.TryParse(inputdate, out datetime);
    }
}

Además de no declarar la variable dt de tipo DateTime y usar la de la misma clase, total para validar si es o no válida, no?

Con lo que nos queda:

bool isValidDate = DateTime.Now.IsDate("18/01/2010");

Podríamos añadir sobrecargas para el formato y demás :)

Un saludo

Publicado por Luis Ruiz Pavón | 4 comment(s)
Archivado en: ,,

[SharePoint 2007] Camino de la Web 2.0 (Ajax Control Toolkit + Rating)

No se sí el título es muy acertado pero no se  me ocurría otra cosa :).

En un portal de publicación estamos añadiendo la parte de Rating, Comments, Cloud Tags, Geolocalización de información… (Web 2.0) y me he decantado por usar para la parte de rating el Ajax Control Toolkit de Microsoft (A partir de ahora ACT) y en concreto el control de Rating que incorpora. Os voy a comentar un poco las peripecias de hacer funcionar todo esto en SharePoint 2007:

Para poder usar el ACT es necesario añadir la librería a la GAC y añadir las siguientes entradas al Web.Config:

  • En el elemento SafeControls:
<SafeControl 
    Assembly="AjaxControlToolkit, Version=3.0.30930.22557, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 
    Namespace="AjaxControlToolkit" 
    TypeName="*" 
    Safe="True" />
  • En el elemento assemblies:
<add 
    assembly="AjaxControlToolkit, Version=3.0.30930.22557, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e"/>
  • En elemento pages –> controls:
<add 
    namespace="AjaxControlToolkit" 
    assembly="AjaxControlToolkit, Version=3.0.30930.22557, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 
    tagPrefix="ajaxToolkit"/>

 

 

Una vez hecho esto, vamos a añadir el ScriptManager a la página maestra de nuestro sitio, para ello lo primero añadir esta directiva de página:

<%@ Register 
    TagPrefix="ajaxToolkit" 
    Assembly="AjaxControlToolkit, Version=3.0.30930.28755, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 
    Namespace="AjaxControlToolkit" %>

Por último añadir el ScriptManager a la página (En mi caso lo he añadido justo después del tag form):

<form id="Form1" runat="server" onsubmit="return _spFormOnSubmitWrapper();">
    <ajaxToolkit:ToolkitScriptManager runat="Server" EnablePartialRendering="true" ID="scriptManager" />

Con esto ya podemos trabajar con esta suite de controles, pero no todo es tan bonito y es que me he encontrado con varios problemas a la hora de trabajar con el control:

1) Cuidado con añadir la propiedad AutoPostback al control

Deja inutilizado el javascript del portal y cambia hasta el título de la página (Un comportamiento raro:S) además sí lo dejamos por defecto el evento Changed se ejecuta sin problemas.

image

Yo lo tengo así en el ascx:

<%@ Register 
    TagPrefix="ajaxToolkit" 
    Assembly="AjaxControlToolkit, Version=3.0.30930.28755, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e" 
    Namespace="AjaxControlToolkit" %>
 
<asp:UpdatePanel runat="server" ID="updatePanel">
    <ContentTemplate>
        <ajaxToolkit:Rating 
            ID="rating" 
            runat="server"
            BehaviorID="RatingBehavior1"
            CurrentRating="0"
            MaxRating="5"
            RatingAlign="Horizontal"
            RatingDirection="LeftToRightTopToBottom"
            StarCssClass="ratingStar"
            WaitingStarCssClass="savedRatingStar"
            FilledStarCssClass="filledRatingStar"
            EmptyStarCssClass="emptyRatingStar"
            OnChanged="UpdateRating"
            style="float: left;" />
    </ContentTemplate>
</asp:UpdatePanel>

2) Bloquear el Rating para que sólo haya una valoración por usuario

Cuando un usuario valora un contenido, lo suyo es deshabilitar el control para evitar que valore más de una vez sobre el mismo contenido. Para ello, el control dispone de una propiedad pública llamada ReadOnly que al ponera a true no podrá modificarse. Sí lo haces a través de del evento OnChanged no te va a funcionar, es decir:

/// <summary>
/// Run custom code when the user rates something and then return a custom string
/// to the JavaScript client
/// </summary>
/// <param name="sender">Rating control</param>
/// <param name="e">RatingEventArgs</param>
protected void Rating_Changed(object sender, RatingEventArgs e)
{
    Rating.ReadOnly = true;
    e.CallbackResult = "Update done. Value = " + e.Value + " Tag = " + e.Tag;
}

 

Para ello, necesitamos hacer uso de JavaScript. Con el siguiente código podrás detectar el CallBack a cliente y deshabilitar el control:

javascript = @"Sys.Application.add_load(function() ;
                {
                    $find('RatingBehavior1').add_EndClientCallback( 
                        function(sender, e) 
                        {
                            $find('RatingBehavior1').set_ReadOnly(true);
                        });
                });"
 
Page.ClientScript.RegisterStartupScript(this.GetType(), "readonly", javascript, true);

 

Lo suyo es chequear en la parte servidora, que sí se hace el rating correctamente del tipo de contenido mandemos un OK al cliente para poder chequearlo en el CallBack y así deshabilitar o no el control.

3) Problemas al hacer click sobre la valoración que está marcada (CurrentRating)

Sí el usuario entra a ver el contenido de la publicación, le mostramos la media de la valoración de todos los usuarios (Por ejemplo 2 estrellitas) sí el quiere valorarlo con 2 estrellas no funcionará, es un error del Rating y he encontrado aquí este script para solucionarlo:

function AddNewRatingHandler() {
 
    AjaxControlToolkit.RatingBehavior.prototype._onStarClick =
        function(e) {
            if (this._readOnly) {
                return;
            }
            //   if (this._ratingValue != this._currentRating) {                    
            this.set_Rating(this._currentRating);
            // }
        };
        AjaxControlToolkit.RatingBehavior.prototype.set_Rating = function(value) {                    
            //   if (this._ratingValue != value) {
            this._ratingValue = value;
            this._currentRating = value;
            if (this.get_isInitialized()) {
                if ((value < 0) || (value > this._maxRatingValue)) {
                    return;
                }
 
                this._update();
 
                AjaxControlToolkit.RatingBehavior.callBaseMethod(this, 'set_ClientState', [this._ratingValue]);
                this.raisePropertyChanged('Rating');
                this.raiseRated(this._currentRating);
                this._waitingMode(true);
 
                var args = this._currentRating + ';' + this._tag;
                var id = this._callbackID;
 
                if (this._autoPostBack) {
                    __doPostBack(id, args);
                }
                else {
                    WebForm_DoCallback(id, args, this._receiveServerData, this, this._onError, true)
                }
 
            }
            //  }
        };                
}
AddNewRatingHandler();

 

 

 

Con todo esto y algo más ya puedo hacer Rating de todos los tipos de contenido del portal:

image

Por suerte todo esto lo tendremos disponible con SharePoint 2010 ;)

Un saludo y hasta después de Reyes que me hace falta un descanso :)

Publicado por Luis Ruiz Pavón | con no comments

[Tips] Modificaciones/Customizaciones no intrusivas en bases de datos

Una de las cosas que me gustó cuando empecé a trabajar con Microsoft Dynamics CRM era la manera en la que se reflejan las customizaciones sobre las entidades. Por ejemplo, la entidad Incidencia tiene su correspondiente tabla dbo.IncidentBase (Con los campos base de dicha entidad) y sí añadimos nuevos campos a esta entidad, no se añaden a esa tabla, sino que lo hace sobre otra llamada dbo.IncidentExtensionBase y así con todas las entidades que pueden ser personalizas. Esto desde mi punto de vista es una muy buena práctica y lo vamos a ver en el siguiente ejemplo:

Imagina que necesitas extender el MembershipProvider de ASP.NET porque necesitas añadir nuevos campos a la tabla de usuarios que te está pidiendo el departamento de HR

¿Cual puede ser el problema de añadir dichos campos a la tabla aspnet_Users?

Pues que sí le da a Microsoft por sacar un Service Pack o realizar modificaciones en nueva versión de NET y hace algo sobre estas tablas, puede ser que nuestros cambios no esten soportados o directamente que lo perdamos, esto se puede dar con otro tipo de base de datos siempre que lo permita el producto y sean modificacione soportadas que no nos hagan perder el soporte técnico.

Además de esta manera tenemos mejor controladas nuestras customizaciones sobre dicha base de datos.

Una buena solución es lo que hace CRM, no tocamos las tabla bases y añadimos nuestra extensión con FK a la tabla de aplicaciones y usuarios:

image

image

¿Qué os parece?

Un saludo y feliz navidad ;)

[Bing Maps] Poner un pushpin al resultado del método Find (where)

Desarrollando un callejero para un portal de SharePoint, me he encontrado la necesidad de añadir un pushpin al resultado del método Find que por defecto no lo hace. Es una tarea sencilla pero en mi caso he tenido que estar mirando la documentación y así lo dejo escrito, que tengo mala memoria y seguro que echo mano de ello alguna vez más :)

Si nos fijamos, el último parámetro del método Find, podemos ver que es una función:

callback : Nombre de la función a la que el servidor llama con los resultados de la búsqueda

¿Cual es la firma de esa función?

function (layer, whatResults, whereResults, hasMore)

En este caso particular, no detenedremos en el parámetro whereResults (También puedes usar whatResults para cuando buscas sitios tales como bares, restaurantes…), que es de tipo VEFindResult, donde podremos acceder a su propiedad LatLong que nos devolverá las coordenadas necesarias para crear el pushpin:

map.AddShape(new VEShape(VEShapeType.Pushpin, whereResults[0].LatLong));

 

A continuación os muestro una imagen y el código completo del ejemplo:

image

Código completo:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
   <head>
      <title></title>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 
      <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
      <script type="text/javascript">
        var map = null;
        var index = 0;
        var results = null;
 
        function GetMap()
        {
            map = new VEMap('myMap');
            map.LoadMap();
        }
 
        function FindLocation()
        {
            map.Clear();
            
            try
            {
               results = map.Find(null,
                                  document.getElementById('txtWhere').value,
                                  null,
                                  null,
                                  null,
                                  null,
                                  true,
                                  true,
                                  true,
                                  true,
                                  onSearchCompleted);
            }
            catch(e)
            {
               alert(e.message);
            }
        }
         
        function onSearchCompleted(layer, whatResults, whereResults, hasMore)
        {
            if(whereResults != null)
            {
                map.AddShape(new VEShape(VEShapeType.Pushpin, whereResults[0].LatLong));
            }
        }
      </script>
</head>
<body onload="GetMap();" style="font-family:Arial">
   <div id='myMap' style="position:relative; width:400px; height:400px;"></div>
   Where:
   <input id="txtWhere" type="text" name="txtWhere" value="Toledo, OH"/>
   <input id="find" type="button" value="Find" name="find" onclick="FindLocation();"/>
</body>
</html>

Un saludo

Publicado por Luis Ruiz Pavón | con no comments
Archivado en: ,,

[MOSS 2007] Cannot retrieve properties at this time + Are you sure you want to navigate away from this page? = Headache

Sí alguna vez te has encontrado con estos 2 mensajes o con alguno de ellos te voy a intentar facilitar la vida un poco contandote mi experiencia:

Seguro que has buscado por internet y has encontrado soluciones para todos los gustos, pues a mí ninguna de ellas me ha funcionado (Para el primer mensajito), aunque si es verdad que alguna me ha dado alguna pista para encontrar la solución :)

Cannot retrieve properties at this time

Al pulsar sobre el botón de “Editor de texto enriquecido”

image

Aparece el dichoso mensaje:

image

Googleando he visto que aparecen distintas soluciones:

  1. Reciclar el pool
  2. Modificar web.config http://blogs.msdn.com/gyorgyh/archive/2009/03/04/troubleshooting-web-part-property-load-errors.aspx

El caso es que la primera (Por lo menos a mí no me ha funcionado) y la segunda de lo que comenta a mí no me aparecen esos valores en los web.config.

Así que con las Developer Tools he revisado la llamada que se estaba haciendo al servicio web que recupera dichas propiedades (como explicaba en uno de los artículos) para revisar el resultado de la llamada al servicio web, y como se aprecia en la imagen no hay respuesta:

image

Revisando el Web.config me he encontrado esto en el elemento httpHandlers:

<httpHandlers>
  <remove verb="GET,HEAD,POST" path="*" />
  <add verb="GET,HEAD,POST" path="*" ...
  <add verb="OPTIONS,PROPFIND,PUT,LOCK,...
  <add verb="*" path="Reserved.Rep...
  <add verb="*" path="*.asmx" ...
  <add verb="*" path="*_AppService.axd"...
  <add verb="GET,HEAD" ...
  <remove verb="*" path="*.asmx" />
</httpHandlers>

Solución: Pues que aunque se están habilitando todos los verbos para los archivos con extensión *.asmx (Servicios Web) al final se están quitando, por lo que quitando el ultimo remove:

image

Ya tenemos respuesta del servicio web y no saldrá la dichosa ventana, perooooooo… ahora nos aparece el siguiente mensaje

Are you sure you want to navigate away from this page?

image

Para esto sí que he encontrado la solución Googleando, que es deshabilitar el soporte nativo de XMLHTTP o lo que es lo mismo deshabilitar el check “Enable native XMLHTTP support”:

image

Un saludo

[Tips] Access Denied en la GAC y como resolverlo a mi manera

Un pequeño truquillo, que aunque puede paracer una tontería (Me ha llevado un rato) pero esta mañana desplegando una dlls en un servidor W2k8 R2 no podía ni instalar, desinstalar, ni drag & drop desde una carpeta a la GAC, puesto que me aparecía el dichoso mensaje:

Failure adding assembly to the cache: Access denied :S

En W2k3, se soluciona haciendo un restart del Servicio de Index Server (Por lo menos en mi caso) pero en W2k8 no viene instalado por defecto y revisando con mi compañero Chan (FicheroPillao.exe XD) quién tenía esa dll pillada, no aparecía ningún proceso.

He leido por internet que puede ser un tema de permisos (Por no usar un Administrador local de máquina) y también he leido que había que ejecutar el comando:

CACLS %WINDIR%\assembly /e /t /p [DOMAIN|MACHINENAME]\useraccount:R

pero tampoco me funcionaba, como tampoco tenía el gacutil para probar, lo he solucionado con XCOPY origen destino.

A ver sí os sirve para no perder el tiempo.

Un saludo

PD: Con el comando XCOPY me he acordado de mi amigo David Hurtado :)

Publicado por Luis Ruiz Pavón | 1 comment(s)
Archivado en:

El mal uso de var

Ultimamente vengo viendo mucho código en el que se hace intensivo el uso de var para declarar todo tipo de variables y me gustaría dar mi opinión sobre este mal uso:

El uso extensivo de var dificulta la legibilidad y comprensión del código fuente, porque un buen código (por lo menos para mí) es el que con leerlo lo podemos entender, por ejemplo:

var i = obj as string;

En este caso concreto se puede observar claramente que el tipo es string (porque lo vemos), pero y en esta caso:

var i = obj;

Pues en esa caso lo mejor podemos hacer es situarnos encima de obj y dejar que el VS nos ilumine con su sabiduría, o por ejemplo en este caso no sabemos que devuelve ese método:

var i = DAL.GetValor();

y no vale la excusa de que es cómodo y que hay clases que son muy largas de escribir, para eso está eso tenemos a Resharper y en su defecto al intellisense de VS :)

Mis 2 céntimos :)

Salu2

Publicado por Luis Ruiz Pavón | 7 comment(s)
Archivado en: ,,

[MOSS 2007] WebParts del tipo ErrorWebPart aparecen cuando usamos SPLimitedWebPartManager

El otro día desarrollando una feature para conectar webparts programáticamente (Escribiré algo sobre esto muy pronto) me encontré un pequeño problema a la hora de listar los webparts de un sitio usando la clase SPLimitedWebPartManager y era que los webparts de tipo ContentByQueryWebPart y los SummaryLink me los devolvía del tipo ErrorWebPart.

Esto se produce cuando ejecutamos el código en la feature receivers o en una aplicación cliente fuera de MOSS, porque probé el código en un webpart y funcionó. Esto me llevo a pensar que algo había que me estaba faltando y claro está que no disponemos en la feature y ni en la aplicación cliente del SPContext, al cual se accede desde dichos webparts para crear las url relativas de las tres hojas de estilo Xsl (MainStyle, HeaderStyle or ItemStyle).

Googleando encontré la solución a dicho problema que es crear un contexto falso:

if (HttpContext.Current == null)
{
    HttpRequest httpRequest = new HttpRequest(String.Empty, web.Url, String.Empty);
    HttpContext.Current = new HttpContext(httpRequest, new HttpResponse(new StringWriter()));
    HttpContext.Current.Items["HttpHandlerSPWeb"] = web;
}

Sí os preguntáis como yo me pregunté por qué en el contexto mete el item HttpHandlerSPWeb, es por qué la clase SPContext en su propiedad estática Current, si vemos el código con reflector hace uso del método GetContextWeb de la clase SPControl:

public static SPContext Current
{
    get
    {
        SPContext context = null;
        if (HttpContext.Current == null)
        {
            return context;
        }
        while (SPControl.GetContextWeb(HttpContext.Current) != null)
        {
            try
            {
                context = GetContext(HttpContext.Current);
            }
            catch (FileNotFoundException)
            {
            }
            return context;
        }
        return null;
    }
}

Y sí miramos el código de SPControl.GetContextWeb():

public static SPWeb GetContextWeb(HttpContext context)
{
    return SPWebEnsureSPControl(context);
}
private static SPWeb SPWebEnsureSPControl(HttpContext context)
{
    SPWeb web = (SPWeb) context.Items["HttpHandlerSPWeb"];
    if (web == null)
    {
        if (context.User == null)
        {
            throw new InvalidOperationException();
        }
        if (SPSecurity.ImpersonatingSelf || (SPSecurity.UserToken != null))
        {
            throw new InvalidOperationException();
        }
        try
        {
            SPSite site;
            context.Items["HttpHandlerSPSite"] = site = new SPSite(SPFarm.Local, SPAlternateUrl.ContextUri, true);
            web = site.OpenWeb();
            context.Items["HttpHandlerSPWeb"] = web;
            SPRequestModule.InitContextWeb(context, web);
            if (!SPContext.GetShouldInitThreadCultureWhenContextWebIsInited(context))
            {
                return web;
            }
            web.SetThreadCultureAfterInit();
        }
        catch (FileNotFoundException)
        {
        }
    }
    return web;
}
 

Ahí podemos ver como sí no tenemos un contexto web y no tenemos el item HttpHandlerSPWeb obtendremos siempre este dichoso error.

Al final lo suyo es que liberemos el contexto:

HttpContext.Current = null;

 

Espero que os sirva, salu2!!!

[Tips] Crear nuestras propias directivas de compilación

Una entradita rápida para ser viernes. El otro día un compañero me dijo que sí sabía como se podían crear directivas de compilación para un tipo de configuración especifica como por ejemplo se hace con la directiva DEBUG:

#if DEBUG
 
Console.WriteLine("Debug");
 
#endif

Los pasos son los siguientes:

dcp

dcp2

image

Nos vamos a las propiedades del proyecto:

image

image

 

 

Una vez creada y configurada y guardada, nos vamos al editor de VS y con la configuración de Debug habilitada vemos que el texto está en gris, eso quiere decir que no estamos en la configuración adecuada para que se cumpla esa directiva de compilación:

 image

Cambiamos la configuración a MyConfiguration:

image

Y probamos:

image

Salu2 y buen finde

Publicado por Luis Ruiz Pavón | 5 comment(s)

[XmlSerializer] El atributo XmlInclude: Aplicando conceptos de POO en la serialización de las clases

Imaginaros que tenéis que almacenar información de los medios de transporte que dispone una empresa, como puede puede ser coches, barcos, aviónes… y dicha información ha de ser serializada en un fichero XML.  Cada entidad especifica como el coche o el avión tienen caracteristicas diferentes, un coche tiene marchas, cilindrada… mientras que un avión podría tener el número de motores que dispone… La pregunta es, ¿Como podemos serializar esta información, de manera que sí añadimos un nuevo medio de transporte, no tengamos que tocar nada de nuestro código de serialización, ni añadir nuevas colecciones ni propiedades a nuestra clases bases?, es decir, hacerlo de una manera genérica y no tener por cada tipo de transporte una lista que contenga objetos de ese tipo. Pues manos a la obra:

Lo primero será crear una clase abstracta que la llamaremos MedioTransporte, a la que añadiremos un a propiedad que tendrán en común que será la marca del transporte, ya que el tipo nos lo dará la propia entidad:

namespace Geeks.Serializer.Entities
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
 
    [Serializable]
    [XmlRootAttribute("transporte")]
    [XmlInclude(typeof(Coche))]
    [XmlInclude(typeof(Avion))]
    public abstract class MedioTransporte
    {
        [XmlAttribute("marca")]
        public string Marca
        {
            get;
 
            set;
        }
    }
}

Ahora creamos 2 medios de transporte, Coche y Avion, que heredan de MedioTransporte:

namespace Geeks.Serializer.Entities
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
 
    [Serializable]
    public class Coche : MedioTransporte
    {
        [XmlAttribute("cilindrada")]
        public int Cilindrada
        {
            get;
 
            set;
        }
 
        [XmlAttribute("marchas")]
        public int Marchas
        {
            get;
 
            set;
        }
    }
}
namespace Geeks.Serializer.Entities
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
 
    [Serializable]
    public class Avion : MedioTransporte
    {
        [XmlAttribute("numeromotores")]
        public int NumeroMotores
        {
            get;
 
            set;
        }
    }
}

 

Y por supuesto, la clase general que es la Empresa:

namespace Geeks.Serializer.Entities
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
 
    [Serializable]
    [XmlRootAttribute("empresa")]
    public class Empresa
    {
        [XmlAttribute("nombre")]
        public string Nombre
        {
            get;
 
            set;
        }
 
        [XmlArray("mediosdetransporte")]
        [XmlArrayItem("mediodetransporte")]
        public List<MedioTransporte> Transportes
        {
            get;
 
            set;
        }
    }
}

Creamos una aplicación de consolo para probar:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using Geeks.Serializer.Entities;
 
namespace Geeks.Serializer
{
    class Program
    {
        static void Main(string[] args)
        {
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
 
            ns.Add(String.Empty, String.Empty);
 
            XmlSerializer serializer =
                new XmlSerializer(typeof(Empresa));
 
            Empresa empresa = new Empresa();
            empresa.Nombre = "Transportes Urgentes NET";
 
            empresa.Transportes = new List<MedioTransporte>();
 
            Coche coche = new Coche();
            coche.Marca = "Seat";
            coche.Cilindrada = 1200;
            coche.Marchas = 5;
 
            empresa.Transportes.Add(coche);
 
            Avion avion = new Avion();
            avion.Marca = "Airbus 380";
            avion.NumeroMotores = 4;
 
            empresa.Transportes.Add(avion);
 
            using (TextWriter writer = new StreamWriter(@"C:\temp\config.xml"))
            {
                serializer.Serialize(writer, empresa, ns);
            }
        }
    }
}

 

Ejecutamos y este es el resultado:

<empresa nombre="Transportes Urgentes NET">
  <mediosdetransporte>
    <mediodetransporte d3p1:type="Coche" 
                       marca="Seat" 
                       cilindrada="1200" 
                       marchas="5" 
                       xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
    <mediodetransporte d3p1:type="Avion" 
                       marca="Airbus 380" 
                       numeromotores="4" 
                       xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
  </mediosdetransporte>
</empresa>

¿Y donde está el truco para que salga el type de la entidad y la POO que se ha aplicado?

La POO la hemos aplicado al crear la clase abstracta MedioTransporte y heredar de ella en Coche y Avion, porque sino hacemos esto tendríamos que haber hecho algo como esto:

namespace Geeks.Serializer.Entities
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
 
    [Serializable]
    [XmlRootAttribute("empresa")]
    public class Empresa
    {
        [XmlAttribute("nombre")]
        public string Nombre
        {
            get;
 
            set;
        }
 
        [XmlArray("coches")]
        [XmlArrayItem("coche")]
        public List<Coche> Coches
        {
            get;
 
            set;
        }
 
        [XmlArray("aviones")]
        [XmlArrayItem("aviones")]
        public List<Avion> Aviones
        {
            get;
 
            set;
        }
    }
}

 

Por cada nuevo medio de transporte, tenemos que estar añadiendo una nueva colección a la clase Empresa y además se serializará en un nuevo elemento, cosa que para mí queda bastante feo, además habría que estar tocando cada 2x3 la clase.

Y aquí es donde entra en juego el atributo XmlInclude, si añadimos un nuevo medio de transporte, solo necesitamos añadir un atributo nuevo a la clase MedioTransporte para que lo serialice, por ejemplo añadimos un Barco:

namespace Geeks.Serializer.Entities
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
 
    [Serializable]
    public class Barco : MedioTransporte
    {
        [XmlAttribute("eslora")]
        public int Eslora
        {
            get;
 
            set;
        }
    }
}
namespace Geeks.Serializer.Entities
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml.Serialization;
 
    [Serializable]
    [XmlRootAttribute("transporte")]
    [XmlInclude(typeof(Coche))]
    [XmlInclude(typeof(Avion))]
    [XmlInclude(typeof(Barco))]
    public abstract class MedioTransporte
    {
        [XmlAttribute("marca")]
        public string Marca
        {
            get;
 
            set;
        }
    }
}

Añadimos a la colección de Transportes un barco:

Barco barco = new Barco();
barco.Eslora = 24;
 
empresa.Transportes.Add(barco);

 

 

Y vemos el XML:

<?xml version="1.0" encoding="utf-8"?>
<empresa nombre="Transportes Urgentes NET">
  <mediosdetransporte>
    <mediodetransporte d3p1:type="Coche" 
                       marca="Seat" 
                       cilindrada="1200" 
                       marchas="5" 
                       xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
    <mediodetransporte d3p1:type="Avion" 
                       marca="Airbus 380" 
                       numeromotores="4" 
                       xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
    <mediodetransporte d3p1:type="Barco" 
                       eslora="24" 
                       xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
  </mediosdetransporte>
</empresa>

A mí me parece una solución bastante elegante.

Adjunto el código de ejemplo.

Salu2

Publicado por Luis Ruiz Pavón | 2 comment(s)
Archivado en: ,,,

[Tips] Quitar los namespaces cuando serializamos con XmlSerializer

Un post corto pero muy útil sí necesitas quitar los namespaces del nodo raíz cuando usas XmlSerializer.

Ejecutando el siguiente código:

XmlSerializer serializer = 
    new XmlSerializer(typeof(Config));
 
using (TextWriter writer = new StreamWriter(@"C:\temp\config.xml"))
{
    serializer.Serialize(writer, config);
}

Obtenemos como resultado:

<?xml version="1.0" encoding="utf-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <solutiond>00000000-0000-0000-0000-000000000000</solutiond>
  <lcid>1033</lcid>
  <projectid>4001</projectid>
  <projecttitle>ProjectTitle</projecttitle>
  <projectdisplayname>ProjectDisplayName</projectdisplayname>
  <projectdescription>ProjectDescription</projectdescription>
  <projectcategory>ProjectCategory</projectcategory>
  <projectthumbnail>ProjectThumbnail.jpg</projectthumbnail>
  <sitemap>
    <sites>
      <Site name="Name" 
            displayname="DisplayName" 
            masterpage="MasterPage" 
            template="Template" 
            includeincurrent="true" 
            includeinglobal="true" 
            inheritcurrent="false" 
            inheritglobal="true" 
            showsiblings="false" 
            showpages="true" 
            showsites="true" />
    </sites>
  </sitemap>
</config>

Como podéis observar, en el nodo config nos aparecen los dichosos namespaces que a veces pueden resultar incomodos. Para quitarlos:

XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
 
ns.Add(String.Empty, String.Empty);
 
XmlSerializer serializer =
    new XmlSerializer(typeof(Config));
 
using (TextWriter writer = new StreamWriter(@"C:\temp\config.xml"))
{
    serializer.Serialize(writer, config, ns);
}

Creamos un objeto de la clase XmlSerializerNamespace, añadimos el prefijo y el namespace vacío y usamos la sobrecarga del método Serialize para pasarle el XmlSerializerNamespace que hemos creado y ahora nuestro XML sin namespaces:

<?xml version="1.0" encoding="utf-8"?>
<config>
  <solutiond>00000000-0000-0000-0000-000000000000</solutiond>
  <lcid>1033</lcid>
  <projectid>4001</projectid>
  <projecttitle>ProjectTitle</projecttitle>
  <projectdisplayname>ProjectDisplayName</projectdisplayname>
  <projectdescription>ProjectDescription</projectdescription>
  <projectcategory>ProjectCategory</projectcategory>
  <projectthumbnail>ProjectThumbnail.jpg</projectthumbnail>
  <sitemap>
    <sites>
      <Site name="Name" 
            displayname="DisplayName" 
            masterpage="MasterPage" 
            template="Template" 
            includeincurrent="true" 
            includeinglobal="true" 
            inheritcurrent="false" 
            inheritglobal="true" 
            showsiblings="false" 
            showpages="true" 
            showsites="true" />
    </sites>
  </sitemap>
</config>

¿Más limpio no? Espero que os sirva.

Salu2 y buen fin de semana a todos!!!

Publicado por Luis Ruiz Pavón | con no comments

[ASP.NET + VS 2008] Web Deployment Projects nos facilita los despliegues de nuestras aplicaciones web

Como dije en mi anterior post, estas 3 últimas semanas, he estado desplegando una solución bastante completa en un cliente y pensaba que no me iba a dar para escribir ningún post, pero olvidé hacer mención de la herramienta:

Visual Studio® 2008 Web Deployment Projects – RTW

Seguro que tienes aplicaciones Web con varios config y settings para entornos diferentes DEV, PRE, PRO y muchas veces tienes que estar cambiando a mano dichos configs, settings… con el consiguiente riesgo de pasar settings de DEV a PRE, PRE a PRO, etc… bueno, pues esta herramienta nos va a facilitar bastante la labor de los despligues, ya que dada una configuración que nosotros mismos podemos crearnos, nos va a generar una solución lista para desplegar con sus settings correspondientes.

Vamos a ver como funciona:

Una vez descargada e instalada, vamos a generar un proyecto de prueba, en mi caso en Servicio Web sencillo y nos vamos al fichero Web.config y añadimos una cadena de conexión:

<connectionStrings>
    <add name="eShopConnectionString" 
         connectionString="Data Source=dev-sql; Initial Catalog=dev-db; Integrated Security=True"/>
  </connectionStrings>

En este caso, estaría apuntando al servidor Sql de Desarrollo. A continuación vamos a añadir a nuestra solución un Web Deployment project:

wdp

A continuación vamos a crear 3 tipos de configuración a nuestro proyecto WDP:

  1. Desarrollo (Debug)
  2. Pre-Producción (Release)
  3. Producción (Release)

wdp2

wdp3

Desarrollo

wdp4

Pre-Producción

wdp5

Producción

wdp6

Una vez creadas las 3 configuraciones, vamos a crear 3 ficheros config con las cadenas de conexión (En este caso solo contendrán una, pero podrían ser más) de cada entorno:

wdp7

Desarrollo

<connectionStrings>
  <add name="eShopConnectionString" 
       connectionString="Data Source=dev-sql; Initial Catalog=dev-db; Integrated Security=True"/>
</connectionStrings>

Pre-Producción

<connectionStrings>
  <add name="eShopConnectionString" 
       connectionString="Data Source=pre-sql; Initial Catalog=pre-db; Integrated Security=True"/>
</connectionStrings>

Producción

<connectionStrings>
  <add name="eShopConnectionString" 
       connectionString="Data Source=pro-sql; Initial Catalog=pro-db; Integrated Security=True"/>
</connectionStrings>

Ahora, solo nos queda especificar para cada configuración de las que hicimos enteriormente, de que fichero de cadenas de conexión obtendrá la configuración:

  1. Seleccionamos la configuración (Desarrollo)
  2. En la opción Deployment
  3. Activamos el check Enable Web.config file section replacement
  4. A la sección connectionStrings de nuestro Web.config lo reemplazamos con lo que contiene el fichero de configuraciones del entorno de desarrollo.
  5. Marcamos el check Enforce matching section replacements

wdp8

Y a las otras 2:

wdp7

wdp10

 

 

Una vez hecho todo el trabajo solo nos queda probar, compilamos la solcuión para cada configuración y nos vamos al directorio de nuestro proyecto y:

wdp11

cada carpeta:

wdp12

lista para desplegar con su configuración correspondiente.

Podemos hacer lo mismo con otras secciones de nuestro Web.config, como por ejemplo las appSettings ;)

La verdad es que esta herramienta me ha gustado bastante, facilita mucho el trabajo a la hora de desplegar, nosotros usabamos otras técnicas pero hemos adoptado a esta nueva herramienta.

Salu2 y a disfrutar!!!

Malas prácticas: SQL Injection + Varios I

Buenos días a todos, hace tiempo que no escribo ningún post decente y no es por falta de tiempo, sino por que actualmente estoy inmerso en un gran despliegue y la verdad es que no me da mucho para escribir sobre ello, espero que mi nuevo proyecto sí me de para compartir mis experiencias que creo que serán útiles para muchos ;)

A día de hoy Viernes 9 de Octubre de 2009 sigo encontrandome código, bien sea en aplicaciones reales y en blogs (Además sí es un blog de renombre visitado por cientos de personas más daño aún) que la verdad da miedo y luego es muy fácil encontrarte gente diciendo que .NET, C#, ASP.NET es una mierda, que es poco seguro…, no señores, no son una mierda, sino quién tira esas líneas de código, por que información al respecto hay muchisiiiiiiiiimmmmmmmmaaaa, solo hay que molestarse en buscar un poco y además este ya es un tema que huele.

Yo no me considero ningún experto, es más, creo que me falta muchiiisssiiiiiimmmmooooo camino por recorrer y aprender de muchísima gente, pero hay cosas que he aprendido y que en este caso vamos a analizar en este post, siempre desde mi punto de vista y que espero debatir amablemente con quién lo desee:

Leyendo mis rss diarias me encuentro este código:

protected void Button1_Click(object sender, EventArgs e)
{
        SqlConnection con = new SqlConnection("Data Source=localhost;Initial Catalog=product; User ID=sa; Password=");
        SqlCommand cmd=new SqlCommand("select * from userdetails where name='"+TextBox1.Text+"' and password ='"+ TextBox2.Text+"'",con);
        con.Open();
        SqlDataAdapter da = new SqlDataAdapter();
        DataTable dt = new DataTable();
        da.SelectCommand = cmd;
        da.Fill(dt);
 
 
        if (dt.Rows.Count != 0)
        {
            Session["UserName"] = TextBox1.Text;
            FormsAuthentication.RedirectFromLoginPage(TextBox1.Text,false);
        }
        else
        {
            // Login Unsuccessful
        }
        con.Close();
 }

No me digáis que no os duele la vista?

  1. No usar el sa (System Adminsitrator) en nuestras cadenas de conexión a la base de datos para evitar problemas de seguridad como en este caso se podrían dar, ya que debido al SQL Injection tendríamos un usuario con todos los permisos para empezar a jugar con la base de datos y con lo que no es la base de datos (Vease xp_cmdshell), en este caso podríamos utilizar un usuario con el role de datareader para hacer consultas de este tipo. Por cierto, en este caso y no es el único que he visto, veo que la password está vacia y no pondría la mano en el fuego de que no tuviera pass ya que en aplicaciones en PRO lo he visto :)
  2. SQL Injection: De esto casi que no hablamos porque se ha hablado ya largo y tendido del tema y se puede encontrar Mb y Mb de información gratis.
  3. SELECT * … no por favor, esto no es una buena práctica ya que si la tabla tiene 200 o más columnas el datatable será monstruoso y a parte, sí quieres saber si ese user existe, recupera sólo la información que necesitas y comprueba por ejemplo que el login que te pasan es el mismo que recuperas por ejemplo. Además de securizar ayudas a mejorar el performance yd ecargar tanto el servidor de SQL como la memoria del servidor Web.
  4. Y con esto llegamos a la 4, comprobar si existe con un count, si nos meten un ‘ or ‘’ = ‘ pues eso dará que el count es mayor que uno y “login succesful”, ale pa dentro!!! por lo menos si comrpobamos que el username que recuperamos es el mismo que nos retorna…

Lo más grave (A parte del código claro está) es el sitio donde me lo he encontrado, que solo diré que se habla mucho y mucho de ASP.NET ;)

Un saludo y a codificar bien y seguro ;)

Publicado por Luis Ruiz Pavón | 6 comment(s)
Archivado en: ,,

[Off Topic] Cerrado por boda y vacaciones hasta el 17 de Agosto

Pues lo dicho, que mañana me caso y cierro hasta el 17 de Agosto. Espero venir con las pilas cargadas (Tengo varios post en el  tintero) y sí empiezo con el nuevo proyecto, prometo escribir a diario sobre las tecnologías que vamos a usar, los problemas que nos encontraremos...

Un saludo y hasta la vuelta!!!

closed_md
Publicado por Luis Ruiz Pavón | 8 comment(s)
Archivado en:

[Spenta] Sí, somos Public Sector Government Partner of the Year

Pues ya puedo hacerlo oficial, hoy 24 de Junio de 2009 Spenta Consulting es Public Sector Government Partner of the Year, y aunque trabajo en dicha empresa, me siento orgulloso de que una empresa Española haya sido galardonada con este premio, fruto del reconocimiento al trabajo que viene realizando.

Public Sector, Government Partner of the Year

Spenta Consulting, Spain

Spenta Consulting developed an extended edition of the Microsoft Citizen Service Platform (CSP) to enable local and regional governments to deliver services effectively and efficiently in multiple channels. With this solution, citizens, civil servants, and politicians will have a better social interaction at a lower cost using the latest Microsoft technologies. The solution adds value with features such as multilanguage and accessibility support, a customizable look and feel, Web 2.0 capabilities, an extension toolkit, a tourism module, and Microsoft Surface applications to provide a new and better user experience. The CSP extended edition has had a major impact on communities, helping them to address a variety of challenges.

http://www.digitalwpc.com/Awards#Public-Sector-Government-Partner-of-the-Year

Enhorabuena a todo el equipo de Spenta!!!

Publicado por Luis Ruiz Pavón | 2 comment(s)
Archivado en: ,,

[VSTO] System.AccessViolationException: Attempted to read or write protected memory

Problema:

vsto_logo_633135670333593750En una de las funcionalidades de un proyecto de VSTO Word en el que estoy echando una mano, me he encontrado el error que hace honor a este post:

System.AccessViolationException: Attempted to read or write protected memory

Concretamente cuando desde Word 2007 se quería abrir mas de un documento haciendo uso de la función:

Application.Documents.Open

Solución:

Mi manera de solucionarlo ha sido usar la siguiente fracción de código:

Marshal.ReleaseComObject(doc);
doc = null;

porque me dá que la función Close del Document no libera bien los recursos del documento y por eso se estaba produciendo el error y según la documentación este método se debe utilizar para liberar adecuadamente el objeto COM.

Salu2

Publicado por Luis Ruiz Pavón | 1 comment(s)
Archivado en: ,
Más artículos Página siguiente >