using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; namespace SLUtils.Classes { public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged { private const string CountString = "Count"; private const string IndexerName = "Item[]"; private const string KeysName = "Keys"; private const string ValuesName = "Values"; private IDictionary<TKey, TValue> _Dictionary; protected IDictionary<TKey, TValue> Dictionary { get { return _Dictionary; } } #region Constructors public ObservableDictionary() { _Dictionary = new Dictionary<TKey, TValue>(); } public ObservableDictionary(IDictionary<TKey, TValue> dictionary) { _Dictionary = new Dictionary<TKey, TValue>(dictionary); } public ObservableDictionary(IEqualityComparer<TKey> comparer) { _Dictionary = new Dictionary<TKey, TValue>(comparer); } public ObservableDictionary(int capacity) { _Dictionary = new Dictionary<TKey, TValue>(capacity); } public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) { _Dictionary = new Dictionary<TKey, TValue>(dictionary, comparer); } public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer) { _Dictionary = new Dictionary<TKey, TValue>(capacity, comparer); } #endregion #region IDictionary<TKey,TValue> Members public void Add(TKey key, TValue value) { Insert(key, value, true); } public bool ContainsKey(TKey key) { return Dictionary.ContainsKey(key); } public ICollection<TKey> Keys { get { return Dictionary.Keys; } } public bool Remove(TKey key) { if (key == null) throw new ArgumentNullException("key"); TValue value; Dictionary.TryGetValue(key, out value); int index = GetIndex(Dictionary, key); var removed = Dictionary.Remove(key); if (removed) OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value), index); //OnCollectionChanged(); return removed; } public bool TryGetValue(TKey key, out TValue value) { return Dictionary.TryGetValue(key, out value); } public ICollection<TValue> Values { get { return Dictionary.Values; } } public TValue this[TKey key] { get { return Dictionary[key]; } set { Insert(key, value, false); } } #endregion #region ICollection<KeyValuePair<TKey,TValue>> Members public void Add(KeyValuePair<TKey, TValue> item) { Insert(item.Key, item.Value, true); } public void Clear() { if (Dictionary.Count > 0) { Dictionary.Clear(); OnCollectionChanged(); } } public bool Contains(KeyValuePair<TKey, TValue> item) { return Dictionary.Contains(item); } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { Dictionary.CopyTo(array, arrayIndex); } public int Count { get { return Dictionary.Count; } } public bool IsReadOnly { get { return Dictionary.IsReadOnly; } } public bool Remove(KeyValuePair<TKey, TValue> item) { return Remove(item.Key); } #endregion #region IEnumerable<KeyValuePair<TKey,TValue>> Members public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return Dictionary.GetEnumerator(); } #endregion #region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)Dictionary).GetEnumerator(); } #endregion #region INotifyCollectionChanged Members public event NotifyCollectionChangedEventHandler CollectionChanged; #endregion #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; #endregion public void AddRange(IDictionary<TKey, TValue> items) { if (items == null) throw new ArgumentNullException("items"); if (items.Count > 0) { if (Dictionary.Count > 0) { if (items.Keys.Any((k) => Dictionary.ContainsKey(k))) throw new ArgumentException("An item with the same key has already been added."); else foreach (var item in items) Dictionary.Add(item); } else _Dictionary = new Dictionary<TKey, TValue>(items); OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray(), GetIndex(Dictionary, items.Keys.First())); //We notify the index of the first added key } } private void Insert(TKey key, TValue value, bool add) { if (key == null) throw new ArgumentNullException("key"); TValue item; if (Dictionary.TryGetValue(key, out item)) { if (add) throw new ArgumentException("An item with the same key has already been added."); if (Equals(item, value)) return; Dictionary[key] = value; OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item), GetIndex(Dictionary, key)); } else { Dictionary[key] = value; OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value), GetIndex(Dictionary, key)); } } private void OnPropertyChanged() { OnPropertyChanged(CountString); OnPropertyChanged(IndexerName); OnPropertyChanged(KeysName); OnPropertyChanged(ValuesName); } private int GetIndex(IDictionary<TKey, TValue> Dictionary, TKey key) { int i = 0; foreach (var dictKey in Dictionary.Keys) { if (dictKey.Equals(key)) return i; } return -1; } protected virtual void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } private void OnCollectionChanged() { OnPropertyChanged(); if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem, int index) { OnPropertyChanged(); if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem, index)); } private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem, int index) { OnPropertyChanged(); if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index)); } private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems, int index) { OnPropertyChanged(); if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems, index)); } public override string ToString() { return string.Format("ObservableDictionary<{0}> Count={1}", GetKeyAndValue(Dictionary.GetType().GetGenericArguments()), Count); } private object GetKeyAndValue(Type[] Types) { string result = string.Empty; foreach (Type type in Types) { result += type.ToString() + ","; } if (!string.IsNullOrEmpty(result) && result.Length > 1) result = result.Substring(0, result.Length - 1); return result; } } }Pruébalo y dime cómo va.
lunes, 10 de diciembre de 2012
ObservableDictionary para Silverlight
He reutilizado copiado el código del Blog de Shimmy pero he tenido que modificarlo un poco para que encajase en Silverlight. De momento solo lo he usado en uno de mis proyectos pero funciona bastante bien.
domingo, 25 de noviembre de 2012
StratexStudio está en La Tienda
Hace un par de días conseguimos por fin certificar y publicar StratexStudio.
Nuestros usuarios de StratexLive podrán conectarse al sitio de sus compañías con la su URL y su usuario de StratexLive y empezar a usar sus Surfaces para enseñarles a sus compañeros lo bien que van sus empresas y lo bonita que es su nuevo aparato con Windows 8.
La nueva aplicación tiene informes para medir el rendimiento, los riesgos y los controles así como la actividad en el portal y una visión anidada de la jerarquía de la organización.
Échale un ojo a la página de la Tienda de Windows de StratexStudio para ver las fotos y, si tienes un dispositivo Windows 8, bájatela y la pruebas, que es gratis..
jueves, 15 de noviembre de 2012
Click funciona pero Tapped causa error en Windows 8
Nos han rechazado la app de la tienda de Windows 8 porque peta cuando el usuario toca o hace Tap o pone su zarpa en una grid view. Como no tenemos una surface estábamos haciendo las pruebas en el simulador, pero con la herramienta del ratón y no con la del dedo y funcionaba perfectamente…
Se cuelga cada vez que tocas en un elemento dentro de una GridView y es curioso porque se cuelga justo en el momento en el que tocas, el código ni siguiera llega a ejecutar el callback. La información del cuelgue tampoco nos da información útil, como era de esperar.
Parece que hay un error en la GridView y tras ser asignada a un DataContext con datos necesita ser refrescada antes de aceptar Taps, aunque funciona bien con los Clicks.
No hay formao yo no la he encontrado de refrescar el UI en WinRT, lo que creo que resolvería el problema, así que he tomado dos caminos diferentes para tratar de evitar el fallo
Los chavales del Windows Store App Lab han dicho que van a buscar una solución al problema pero yo creo que este es uno de esos errores que se arregla con un paquete en el Windows Update… Os mantendré informados si se inventan algo nuevo.
***ACTUALIZACIÓN***
Un experto de Microsoft me ha apuntado que si creas y añades los datos a la GridView en el código y después la añades a la página funciona correctamente. Lo he probado y es verdad.
Los templates en mis GridViews son complejos así que haré UserControls con las GridViews de manera que pueda crearlas con datos en código sin perder la posibilidad de seguir usando MVVM.Esto todavía no lo he probado.
Se cuelga cada vez que tocas en un elemento dentro de una GridView y es curioso porque se cuelga justo en el momento en el que tocas, el código ni siguiera llega a ejecutar el callback. La información del cuelgue tampoco nos da información útil
Parece que hay un error en la GridView y tras ser asignada a un DataContext con datos necesita ser refrescada antes de aceptar Taps, aunque funciona bien con los Clicks.
No hay forma
- Informes de carga rápida: Estoy pre-cargando los contenidos de los informes que cargan rápido cuando se ejecuta la app. Haciendo esto relleno todas las GridViews en páginas diferentes de las que el usuario está viendo, en segundo plano. Una vez que se selecciona la página en donde reside la GridView ésta es renderizada junto con el resto de la página y se solventa el problema.
- Informes de carga lenta: También pre-cargo el contenido de estos informes, pero si el usuario los muestra antes de que se rellene la GridView con lo que los datos se mostrarían pero el control no se refrescaría y el Tapped seguiría fallando. Para evitar esto he cambiado también el comportamiento de la carga de datos. Asigno el DataContext antes y lo actualizo varias veces, una vez por cada paquete de información que recibo. Así el GridView se refresca unas cuantas veces y se soluciona el problema con el Tapped.
Los chavales del Windows Store App Lab han dicho que van a buscar una solución al problema pero yo creo que este es uno de esos errores que se arregla con un paquete en el Windows Update… Os mantendré informados si se inventan algo nuevo.
***ACTUALIZACIÓN***
Un experto de Microsoft me ha apuntado que si creas y añades los datos a la GridView en el código y después la añades a la página funciona correctamente. Lo he probado y es verdad.
Los templates en mis GridViews son complejos así que haré UserControls con las GridViews de manera que pueda crearlas con datos en código sin perder la posibilidad de seguir usando MVVM.
martes, 6 de noviembre de 2012
Conectando VS2010 al nuevo tfs.visualstudio.com (TFS2012)
Tiene buena pinta y es gratis para equipos de 5 o menos miembros, (parece que será gratis también para equipos más grandes con la suscripción al MSDN) ¿Cómo es que todavía no lo estamos usando?
En realidad si que lo usamos para la app de Windows Metro pero para nuestro código de SharePoint 2010 todavía usamos el viejo TFS2010.
Tenemos documentación documentation sobre cómo conectar VS2010 al nuevo TFS pero, como siempre, me temo que no será simple.
Nuestros Visual Studios 2010 ya están actualizados con el Service Pack 1 así que todo lo que necesitamos es el KB2662296.
Este parche puede ser instalado desde windows update o descargado convenientemente desde aquí.
La primera vez que ejecuté el fichero se quejó de que mi ordenador no cumplía con alguno de los requisitos, comprobé que tenía el SP1 instalado y lo estaba, así que ejecuté otra vez el fichero y funcionó la segunda vez.Todo el mundo sabe que el software debe funcionar por lo menos una de cada tres veces para que sea considerado entregable.
Se levó un buen rato para instalar y al final hubo que reiniciar la máquina.
Después de la instalación y el reseteo intenté conectar al nuevo team project desde el link de “Abrir nueva instancia de Visual Studio”, como se especifica en las instrucciones, pero no funcionó.
Vale, la próxima cosa a probar es añadir el servidor de team foundation desde el VS2010 como siempre… CTRL+C, CTRL+V de la url y pulsar siguiente… ¡Y funcionó!
¡Estoy deseando ver la gráfica del burndown mostrando el avance del equipo!
En realidad si que lo usamos para la app de Windows Metro pero para nuestro código de SharePoint 2010 todavía usamos el viejo TFS2010.
Tenemos documentación documentation sobre cómo conectar VS2010 al nuevo TFS pero, como siempre, me temo que no será simple.
Nuestros Visual Studios 2010 ya están actualizados con el Service Pack 1 así que todo lo que necesitamos es el KB2662296.
Este parche puede ser instalado desde windows update o descargado convenientemente desde aquí.
La primera vez que ejecuté el fichero se quejó de que mi ordenador no cumplía con alguno de los requisitos, comprobé que tenía el SP1 instalado y lo estaba, así que ejecuté otra vez el fichero y funcionó la segunda vez.
Después de la instalación y el reseteo intenté conectar al nuevo team project desde el link de “Abrir nueva instancia de Visual Studio”, como se especifica en las instrucciones, pero no funcionó.
Vale, la próxima cosa a probar es añadir el servidor de team foundation desde el VS2010 como siempre… CTRL+C, CTRL+V de la url y pulsar siguiente… ¡Y funcionó!
¡Estoy deseando ver la gráfica del burndown mostrando el avance del equipo!
miércoles, 3 de octubre de 2012
Un Thread.Sleep que no cuelga el hilo
He estado añadiendo tests unitarios a mi código de SharePoint y no es una tarea facil.
Una de las cosas más importantes que quería probar eran unos ItemEventReceivers de una lista. Esto funciona sin problemas cuando pruebas los eventos síncronos como el ItemAdding o el ItemUpdating pero cuando se trata de probar los eventos asíncronos es necesario esperar...
Si pones el hilo del test a dormir los eventos asíncronos no se ejecutarán ya que es el mismo en el que se ejecuta el código de SharePoint y aunque tu código funcione cuando lo ejecutas a mano los tests fallarán.
Es por esto que he creado una clase basada en timers que no cuelga el hilo dejando ejecutarse los eventos asíncronos.
Una de las cosas más importantes que quería probar eran unos ItemEventReceivers de una lista. Esto funciona sin problemas cuando pruebas los eventos síncronos como el ItemAdding o el ItemUpdating pero cuando se trata de probar los eventos asíncronos es necesario esperar...
Si pones el hilo del test a dormir los eventos asíncronos no se ejecutarán ya que es el mismo en el que se ejecuta el código de SharePoint y aunque tu código funcione cuando lo ejecutas a mano los tests fallarán.
Es por esto que he creado una clase basada en timers que no cuelga el hilo dejando ejecutarse los eventos asíncronos.
public class Waiter : IDisposable { public enum WaiterState { Waiting, TimedOut, Success, Error }; System.Timers.Timer WaitTimer; ManualResetEvent manualResetEvent; int WaitCounter; private Waiter(int interval) { WaitCounter = 0; manualResetEvent = new ManualResetEvent(true); WaitTimer = new System.Timers.Timer() { AutoReset = false, Interval = interval }; WaitTimer.Elapsed += new ElapsedEventHandler(WaitTimer_Elapsed); } void WaitTimer_Elapsed(object sender, ElapsedEventArgs e) { WaitCounter++; manualResetEvent.Set(); } ///Pruébala./// Waits for the interval in milliseconds times number of times or once by default. /// public static WaiterState Wait(int interval, int times) { try { using (Waiter WaiterClass = new Waiter(interval)) { while (WaiterClass.WaitCounter <= times) { WaiterClass.WaitTimer.Start(); WaiterClass.manualResetEvent.WaitOne(); } } } catch { return WaiterState.Error; } return WaiterState.Success; } ////// Waits for the interval in milliseconds once. /// public static WaiterState Wait(int interval) { return Wait(interval, 0); } void Dispose() { WaitTimer.Dispose(); } }
Refrescando el SharePoint 2010 Timer Service mediante código
Necesito refrescar el servicio de Timer desde una aplicación y me ha costado bastante encontrar cómo hacerlo. Es tan facil como te imaginas
Aquí está el código:
Aquí está el código:
///Por cierto, las constantes son:/// Stops the SharePoint timer. /// public static void TimerStop() { ServiceController timerService = new ServiceController(Constants.SPTimerName); if (timerService.Status == ServiceControllerStatus.Running) { timerService.Stop(); timerService.WaitForStatus(ServiceControllerStatus.Stopped, Constants.WaitingTimeout); } } ////// Starts the SharePoint timer. /// public static void TimerStart() { ServiceController timerService = new ServiceController(Constants.SPTimerName); if (timerService.Status == ServiceControllerStatus.Stopped) { timerService.Start(); timerService.WaitForStatus(ServiceControllerStatus.Running, Constants.WaitingTimeout); } }
public static TimeSpan WaitingTimeout = new TimeSpan(0, 1, 30); public static string SPTimerName = "SharePoint 2010 Timer";
jueves, 6 de septiembre de 2012
Autenticando un Web Service ASMX en SharePoint desde una aplicación WinRT
¿Dónde está el CookieContainer?
Sí amigo, yo también me he preguntado eso.
Estamos creando una aplicación para Windows 8 y necesitamos que se conecte a nuestros servicios web. Conectarse de manera anónima es fácil pero, cuando se trata de conectarse autenticando el usuario, la cosa cambia.
Yo estaba muy seguro, seguro del verbo ignorante y cándido, de que podría usar el mismo método que usaba para crear aplicaciones de WP7. Lo he hecho mil veces y WP7 es más o menos lo mismo que WinRT así que ¿Quién habría pensado que no funcionaría?
Bien, WinRT no es lo mismo que WP7.
Después de crear un SPAuthBridge intenté añadir la cookie de autenticación a mi SoapClient como siempre pero, como probablemente sabes si estás leyendo este post, esta línea no funciona:
En WinRT el objeto SoapClient no tiene CookieContainer asi que tenemos que buscarnos otra manera de añadir la cookie en las llamadas.
El SPAuthBridge va bien. Solo he añadido un método nuevo para obtener la cookie de autenticación del CookieContainer.
Después de eso, solo tienes que seguir los pasos del otro post para autenticarte y después de haberte autenticado correctamente en SharePoint tienes que cambiar la manera en la que añades la cookie a tu SoapClient.
Yo he encontrado dos formas:
La rápida
Cada vez que necesites llamar al servicio web necesitaras añadir la cookie al header y entonces llamar al servicio dentro del mismo OperationContextScope. Más o menos así:
Esto es fácil pero no muy legible y si tienes que llamar al web service unas cuantas veces, seguramente, te vas a aburrir de hacerlo así.
La elegante
En lugar de añadir la cookie cada vez podemos crearnos un Behaviour que se encargue de hacerlo automáticamente.
Para hacerlo necesitas crearte una clase CookieBehavior. Yo he copiado el 99% de la mía de aquí y es así:
Cuando tenemos esta clase ya solo tememos que añadir el Behavior al servicio web en la inicialización y la cookie se añadirá solita cada vez.
Después de haber inicializado el servicio web de esta manera puedes llamar a los métodos sin preocuparte de la autenticación nunca más:
El MaxReceivedMessageSize quizás podría seruna mijita excesivo. Configúralo para que se adapte a tus necesidades pero, ten en cuenta que si lo pones demasiado bajo te va a dar una CommunicationException diciendo que:
Diviértete con la tabletsi es que la tienes, yo estoy todavía esperando la mía.
Sí amigo, yo también me he preguntado eso.
Estamos creando una aplicación para Windows 8 y necesitamos que se conecte a nuestros servicios web. Conectarse de manera anónima es fácil pero, cuando se trata de conectarse autenticando el usuario, la cosa cambia.
Yo estaba muy seguro
Bien, WinRT no es lo mismo que WP7.
Después de crear un SPAuthBridge intenté añadir la cookie de autenticación a mi SoapClient como siempre pero, como probablemente sabes si estás leyendo este post, esta línea no funciona:
MySoapClient.CookieContainer = SharePointAuth.cookieJar;
En WinRT el objeto SoapClient no tiene CookieContainer asi que tenemos que buscarnos otra manera de añadir la cookie en las llamadas.
El SPAuthBridge va bien. Solo he añadido un método nuevo para obtener la cookie de autenticación del CookieContainer.
public string GetAuthenticationCookie() { return string.Format("{0}={1}", "FedAuth", cookieJar.GetCookies(new System.Uri("https://www.mySite.com"))["FedAuth"].Value); }
Después de eso, solo tienes que seguir los pasos del otro post para autenticarte y después de haberte autenticado correctamente en SharePoint tienes que cambiar la manera en la que añades la cookie a tu SoapClient.
Yo he encontrado dos formas:
La rápida
Cada vez que necesites llamar al servicio web necesitaras añadir la cookie al header y entonces llamar al servicio dentro del mismo OperationContextScope. Más o menos así:
using (new OperationContextScope(MyWS.InnerChannel)) { HttpRequestMessageProperty httpRequestProperty = new HttpRequestMessageProperty(); httpRequestProperty.Headers[System.Net.HttpRequestHeader.Cookie] = SharePointAuth.GetAuthenticationCookie(); OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty; UserInfo = await MyWS.GetInfoForUserAsync(); }
Esto es fácil pero no muy legible y si tienes que llamar al web service unas cuantas veces, seguramente, te vas a aburrir de hacerlo así.
La elegante
En lugar de añadir la cookie cada vez podemos crearnos un Behaviour que se encargue de hacerlo automáticamente.
Para hacerlo necesitas crearte una clase CookieBehavior. Yo he copiado el 99% de la mía de aquí y es así:
class CookieBehaviour: IEndpointBehavior { private string cookie; public CookieBehaviour(string cookie) { this.cookie = cookie; } public void AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime behavior) { behavior.ClientMessageInspectors.Add(new CookieMessageInspector(cookie)); } public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint serviceEndpoint) { } } public class CookieMessageInspector : IClientMessageInspector { private string cookie; public CookieMessageInspector(string cookie) { this.cookie = cookie; } public void AfterReceiveReply(ref Message reply, object correlationState) { } public object BeforeSendRequest(ref Message request, IClientChannel channel) { HttpRequestMessageProperty httpRequestMessage; object httpRequestMessageObject; if (request.Properties.TryGetValue(HttpRequestMessageProperty.Name , out httpRequestMessageObject)) { httpRequestMessage = httpRequestMessageObject as HttpRequestMessageProperty; } else httpRequestMessage = new HttpRequestMessageProperty(); httpRequestMessage.Headers[System.Net.HttpRequestHeader.Cookie] = cookie; request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage); return null; } }
Cuando tenemos esta clase ya solo tememos que añadir el Behavior al servicio web en la inicialización y la cookie se añadirá solita cada vez.
public MyWSSoapClient InitializeMyWS() { BasicHttpBinding Bind = new BasicHttpBinding(); //////////////////////Transport/////////////////////// Bind.Security.Mode = BasicHttpSecurityMode.Transport; ////////////////////////////////////////////////////// /////////////////////MessageSize////////////////////// Bind.MaxReceivedMessageSize = Int32.MaxValue; ////////////////////////////////////////////////////// EndpointAddress oAddress = new EndpointAddress(SiteUrl + "/_vti_bin/MyWS.asmx"); MyWSSoapClient MyWS = new MyWSSoapClient(Bind, oAddress); CookieBehaviour AuthCookieBehaviour = new CookieBehaviour(SharePointAuth.GetAuthenticationCookie()); MyWS.Endpoint.EndpointBehaviors.Add(AuthCookieBehaviour); return MyWS; }
Después de haber inicializado el servicio web de esta manera puedes llamar a los métodos sin preocuparte de la autenticación nunca más:
private async Task<ObservableCollection<Info>> GetInfo() { GetInfoForUserResponse Information = await MyWS.GetInfoForUserAsync(); return Information.Body.GetInfoForUserResult; }En el método inicializador el poner el SecurityMode del BasicHttpBinding a Transport es necesario porque la aplicación web en la que se aloja el servicio web es HTTPS. Por defecto el SecurityMode está puesto a None y me estaba dando una ArgumentException como esta:
The provided URI scheme 'https' is invalid; expected 'http'.
El MaxReceivedMessageSize quizás podría ser
The maximum message size quota for incoming messages (65536) has been exceeded. To increase the quota, use the MaxReceivedMessageSize property on the appropriate binding element.
Diviértete con la tablet
miércoles, 5 de septiembre de 2012
Snippet para DependencyProperty con método Call-Back y valor por defecto
Para mi esta es la manera en la que Microsoft debería haber hecho el snippet de DependencyProperty desde el principio.
Uso este un montón y tiene definidos lugares para el valor por defecto, el método call-back e incluso asigna la instancia de la clase que llama al call-back a una variable automaticamente.
Por cierto, podéis encontrar más información sobre cómo crear e importar snippets aquí.
Uso este un montón y tiene definidos lugares para el valor por defecto, el método call-back e incluso asigna la instancia de la clase que llama al call-back a una variable automaticamente.
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>Dependency Property With CallBack</Title> <Shortcut>propdpCallBack</Shortcut> <Description>Creates a dependency property with it's callback method</Description> <Author>Jose Sanchez</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> <SnippetType>SurroundsWith</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>PublicName</ID> <Default>PropertyName</Default> <ToolTip>Public name of the depencency property</ToolTip> </Literal> <Literal> <ID>type</ID> <Default>string</Default> <ToolTip>Type of the property</ToolTip> </Literal> <Literal> <ID>ParentClassType</ID> <Default>UserControl</Default> <ToolTip>Type of the parent class of the dependency property</ToolTip> </Literal> <Literal> <ID>DefaultValue</ID> <Default>string.Empty</Default> <ToolTip>Default value of the dependency property</ToolTip> </Literal> <Literal> <ID>CallBackName</ID> <Default>PropertyCallBackName</Default> <ToolTip>Name of the callback method that will be triggered when the depencency property has changed</ToolTip> </Literal> </Declarations> <Code Language="csharp"><![CDATA[public $type$ $PublicName$ { get { return ($type$)GetValue($PublicName$Property); } set { SetValue($PublicName$Property, value); } } // Using a DependencyProperty as the backing store for $PublicName$. This enables animation, styling, binding, etc... public static readonly DependencyProperty $PublicName$Property = DependencyProperty.Register("$PublicName$", typeof($type$), typeof($ParentClassType$), new PropertyMetadata($DefaultValue$, new PropertyChangedCallback($CallBackName$))); private static void $CallBackName$(DependencyObject obj, DependencyPropertyChangedEventArgs e) { $ParentClassType$ Changed$ParentClassType$ = (obj as $ParentClassType$); //TODO: Implement some actions in Changed$ParentClassType$ }]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets>Espero que os sea tan útil como a mi.
Por cierto, podéis encontrar más información sobre cómo crear e importar snippets aquí.
jueves, 5 de abril de 2012
Esconder Editar en hoja de datos en el menu Acciones con código javascript
El título del post explica bastante bien cual era el objetivo a cumplir y todo el mundo sabe que esta no es la mejor forma, pero editar los permisos estaba dándonos problemas en otro lado así que decidimos hacerlo asi.
Este es el enemigo:
Y para ocultarlo agregamos un Web Part Editor de Contenido e hicimos click en el botón Editar código fuente:
Allí añadimos el script:
<script type="text/javascript" > var allMenuItems = document.getElementsByTagName('ie:menuitem'); for(var i = 0; i < allMenuItems.length; i++ ) { try { if (allMenuItems[i].text.toLowerCase() == "editar en hoja de datos") { var parentNodeOfMenuItem = allMenuItems[i].parentNode; parentNodeOfMenuItem.removeChild(allMenuItems[i]); } } catch(err) {} } </script>
Puede parecer un poco desordenado, pero a SharePoint no le importará. Después hacemo click en guardar.
Después de eso podemos poner el web part oculto para que los usuarios no puedan verlo.
Y eso es todo.
Enjoy!
sábado, 25 de febrero de 2012
CrossDomain SecurityException accediendo a mi Servicio WCF en Azure WCF desde Silverlight
Acabo de subir una Rol Web de Servicio WCF a Azure con la idea de acceder a el desde un web part Silverlight, pero me saltó la temida y casi esperada:
que acabo de enterarme de que existe?
Para mi sorpresa es muy fácil sólo tienes que añadir los XMLs al proyecto:
Después de esto podrás depurar el código y después de que actualices el paquete en el "Hosted Service" de Azure podrás acceder sin problemas desde tu Silverlight.
Por cierto, ¿todo el tema este de la gestión de Azure está traducido al castellano?
System.ServiceModel.CommunicationException: An error occurred while trying to make a request to URI 'http://localhost:81/MyService.svc'. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. This error may also be caused by using internal types in the web service proxy without using the InternalsVisibleToAttribute attribute. Please see the inner exception for more details. ---> System.Security.SecurityException ---> System.Security.SecurityException: Security error.Vale, bien, pero ¿Cómo añado mi fichero crossdomain.xml o el clientaccesspolicy.xml a un “Rol Web de Azure”
Después de esto podrás depurar el código y después de que actualices el paquete en el "Hosted Service" de Azure podrás acceder sin problemas desde tu Silverlight.
lunes, 20 de febrero de 2012
MessageBox, DialogBox y ventanas Popup en Silverlight con el control ChildWindow
Ya sé que esto lleva ahí desde Silverlight 3, ¡pero yo no lo sabía! ¿De verdad? No me lo puedo creer...
Así que para mi esto es nuevo, y tiene muy buena pinta. Yo siempre había creado controles de usuario para estas cosas, pero este ChildWindow tiene hasta animaciones…
Crearlo es tan simple como añadir un elemento de este tipo a nuestro proyecto:
Después solo tienes que modificar un poco el XAML para que tenga el aspecto que necesitas y listo.
Para usarlo tienes que crear el objeto y mostrarlo así:
Como siempre, hay un problema. Los objetos MessageBox o DialogBox paran el hilo esperando una respuesta, mientras que el control ChildWindow no (esto también es bueno en algunos casos, de hecho he notado últimamente que los MessageBox consumen un montón de recursos) así que si necesitas usar la respuesta que el usuario ha seleccionado en tu ChildWindow tendrás que suscribirte al evento Close, algo como esto:
Yo he cambiado un poco el código por defecto. Me ha quedado así::
Por cierto, haypor lo menos otro problema. Estos controles no funcionan si se llaman en el constructor… solo funcionan después de que el control haya cargado completamente (http://vanderbiest.org/blog/2010/08/03/silverlight-4-childwindow-is-not-visible/)
Así que para mi esto es nuevo, y tiene muy buena pinta. Yo siempre había creado controles de usuario para estas cosas, pero este ChildWindow tiene hasta animaciones…
Crearlo es tan simple como añadir un elemento de este tipo a nuestro proyecto:
Después solo tienes que modificar un poco el XAML para que tenga el aspecto que necesitas y listo.
Para usarlo tienes que crear el objeto y mostrarlo así:
MessageWindow window = new MessageWindow("Info!", string.Format("This is a ChildWindow example."), true); window.Show();
MessageWindow window = new MessageWindow("Info!", string.Format("This is a ChildWindow example."), true); window.Show(); window.Closed += (sndr, args) => { if (window.DialogResult == true) { StratexWS.GetFBAUsersAsync(); LoadingWindow.Show(); } };
Yo he cambiado un poco el código por defecto. Me ha quedado así::
Por cierto, hay
martes, 14 de febrero de 2012
Métodos Extensores para Objetos Null
¿Es posible usar un método extensor en algo que es null?
Qué bonito sería poder hacer algo como:
Así que me he estado conteniendo para no hacerlo todo este tiempo hasta hoy que me he sentido valiente.
Los métodos extensores:
obviamente todo buen programa de pruebas debe ser una aplicación de consola:
Esto abre una nueva dimensión a mis recetas de espagueti.
El primer ejemplo útil que se me viene a la cabezaaparte del ToStringSafe y del ToInt, ToDateTime... ¡Mírame!, ¡No puedo parar! es:
A veces me asusta que me den tanta alegría estas cosas…
Qué bonito sería poder hacer algo como:
if (!MyString.IsNull()) return MyString.ToString();Leí hace unos cuantos años que hacer eso sería imposible porque, al ser el objeto null no podrías llamar al método o algo así… Pero en mi cabeza tenía sentido.
Así que me he estado conteniendo para no hacerlo todo este tiempo hasta hoy que me he sentido valiente.
Los métodos extensores:
public static bool IsNull(this string str) { return str == null; } public static string ToStringSafe(this object obj) { return (obj ?? string.Empty).ToString(); }El método main
static void Main(string[] args) { string str = null; if (str.IsNull()) Console.WriteLine("It was null..."); else Console.WriteLine("It wasn't null..."); Console.WriteLine(str.ToStringSafe()); Console.ReadKey(); }¡Y funciona!
El primer ejemplo útil que se me viene a la cabeza
///Con este método nunca más tendremos que preocuparnos de si el objeto es null o no. Todo se libera sin problemas./// Disposes the object if it's not null. /// public static void DisposeSafe(this IDisposable DisposableObject) { if (DisposableObject != null) DisposableObject.Dispose(); }
miércoles, 8 de febrero de 2012
Comprimir Todas las Bases de Datos
No recomendaría hacer esto en un entorno de producción no suena mucho a best practices, pero si tu entorno de desarrollo se está quedando sin espacio en el disco duro creo que es una buena alternativa a comprimir cada base de datos con el ratón una a una…
El código SQL que uso es este:
El código SQL que uso es este:
declare @db varchar(255)declare c cursor for select name from sys.databases where is_read_only=0 and state=0 and name not in ('master','model','tempdb','msdb') open c fetch c into @db while @@fetch_status=0 begin exec SP_dboption @db,'trunc. log on chkpt.','true' DBCC shrinkdatabase (@db) fetch next from c into @db end close c deallocate c
(Cogí el código de aquí pero el mío está bien formateado :P)
jueves, 2 de febrero de 2012
Extendiendo, Overridando y Clases Base
Hace un par de días mi primo me preguntó sobre el significado de la palabra override. Intenté explicárselo con un par de ejemplos tontos y los dos sonaron bastante estúpidos… Bien, este es un ejemplo real.
Queremos crear web parts Silverlight y que todos ellos compartan las mismas propiedades básicas y el mismo método render. Así que extendiendo Microsoft.SharePoint.WebPartPages.WebPart crearemos un BaseSilverlightWebPart con estas características (overridandosí sí, has leído bien, overridando, nada de sobreescribiendo sobreescribir es irte a la clase base, borrar el metodo y escribir encimael método render de la clase WebPart) y después lo usaremos para crear otros web parts fácilmente.
La clase base quedó así:
¿otra vez? nada si no te hace falta. El método render es exactamente igual que en la base por lo que no necesitamos ni nombrarlo. La clase usará el método de su clase base automáticamente. Mira cuánto código estamos ahorrándonos de escribir otra vez, todas las propiedades, el render gordo de la clase base, etc. Solo cambiamos lo que necesitamos cambiar.
También podemos añadir propiedades nuevas como aquí:
toma ya, doble tirabuzón, carpado y mortal hacia atrás de palabro inventado.
¿otra? en la página.
Y esta es la pinta que tiene el override en la vida real. Ahora sí que tiene sentido…¿o no?
Queremos crear web parts Silverlight y que todos ellos compartan las mismas propiedades básicas y el mismo método render. Así que extendiendo Microsoft.SharePoint.WebPartPages.WebPart crearemos un BaseSilverlightWebPart con estas características (overridando
La clase base quedó así:
public abstract class BaseSilverlightWebpart : Microsoft.SharePoint.WebPartPages.WebPart { #region Web Part Properties [Personalizable(PersonalizationScope.Shared)] [WebBrowsable(true)] [System.ComponentModel.Category("Stratex")] [WebDisplayName("XAP List URL")] [Description("Select the URL of the XAP list.")] public string XAPListUrl { get; set; } #endregion #region Private Properties Dictionary<string, string> InitParams; #endregion #region Abstract /// <summary> /// Setup here the of the XAP you will use /// </summary> public abstract string XAPName { get; } /// <summary> /// Setup here the initial parameters you will use in your Silverlight Web Part /// </summary> public abstract void SetUpParameters(); #endregion #region Methods public void AddParameter(string Name, string Value) { if (InitParams == null) InitParams = new Dictionary<string, string>(); if (InitParams.ContainsKey(Name)) InitParams[Name] = Value; else InitParams.Add(Name, Value); } #endregion #region Overrides protected override void CreateChildControls() { SetUpParameters(); if (string.IsNullOrEmpty(XAPListUrl)) XAPListUrl = string.Format("{0}/Lists/XAPLibrary/", SPContext.Current.Web.ServerRelativeUrl); //Sometimes when you create the web part it's 0px by 0px... ¬ ¬ if (string.IsNullOrEmpty(Height)) Height = "150px"; if (string.IsNullOrEmpty(Width)) Width = "150px"; LiteralControl obj = new LiteralControl(); obj.Text = "<object id='silverlightHost' height='" + Height + "' width='" + Width + @"' data='data:application/x-silverlight-2,' type='application/x-silverlight-2' style='display:block' class='ms-dlgDisable'> <param name='Source' value='" + XAPListUrl + XAPName + @"' /> <param name='MinRuntimeVersion' value='3.0.40624.0' /> <param name='Background' value='#00FFFFFF' /> <param name='windowless' value='true' /> <param name='autoUpgrade' value='true' /> "; if (InitParams.Count > 0) { obj.Text +="<param name='initParams' value='"; int i = 0; foreach (var param in InitParams) { if (i++ == 0) obj.Text += string.Format("{0}={1}", param.Key, param.Value); else obj.Text += string.Format(", {0}={1}", param.Key, param.Value); } obj.Text += @"' /> "; } obj.Text += @"<a href='http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0' style='text-decoration: none;'> <img src='http://go.microsoft.com/fwlink/?LinkId=108181' alt='Click here to install Silverlight' style='border-style: none'/> </a> </object>"; this.Controls.Add(obj); } #endregion }Extendiéndola podemos crear un web part Silverlight fácilmente:
public class DynamicStrategyMap : BaseSilverlightWebpart { #region Overrides public override string XAPName { get { return "StratexPointStrategyMap.xap"; } } public override void SetUpParameters() { AddParameter("site", HttpUtility.UrlEncode(SPContext.Current.Web.ServerRelativeUrl)); } #endregion }Fíjate que no tienes que overridar
También podemos añadir propiedades nuevas como aquí:
public class StratexHeartBeat : BaseSilverlightWebpart { #region WebPartProperties [Personalizable(PersonalizationScope.Shared)] [WebBrowsable(true)] [System.ComponentModel.Category("Stratex")] [WebDisplayName("NumberOfEvents")] public string NumberOfEvents { get; set; } [Personalizable(PersonalizationScope.Shared)] [WebBrowsable(true)] [System.ComponentModel.Category("Stratex")] [WebDisplayName("TimerLapse")] public string TimerLapse { get; set; } [Personalizable(PersonalizationScope.Shared)] [WebBrowsable(true)] [System.ComponentModel.Category("Stratex")] [WebDisplayName("Indicator Summary Url")] public string IndicatorSummaryUrl { get; set; } #endregion #region Overrides public override string XAPName { get { return "StratexHeartBeat.xap"; } } public override void SetUpParameters() { AddParameter("site", HttpUtility.UrlEncode(SPContext.Current.Web.Url)); if (string.IsNullOrEmpty(IndicatorSummaryUrl)) IndicatorSummaryUrl = string.Format("{0}/Lists/WebPartPages/IndicatorSummary.aspx", SPContext.Current.Web.ServerRelativeUrl); AddParameter("indicatorsummaryurl", IndicatorSummaryUrl); if (Common.ConvertToInt(NumberOfEvents) < 1) NumberOfEvents = "1"; AddParameter("numberofnews", NumberOfEvents); if (Common.ConvertToInt(TimerLapse) < 1) TimerLapse = "1"; AddParameter("timerlapse", TimerLapse); } #endregion }O incluso podemos añadir más código al método render overridando el método render overridado en la clase base
public class Commentary : BaseSilverlightWebpart { #region Overrides public override string XAPName { get { return "Commentary.xap"; } } public override void SetUpParameters() { AddParameter("site", HttpUtility.UrlEncode(SPContext.Current.Web.ServerRelativeUrl)); } protected override void CreateChildControls() { base.CreateChildControls(); //We are using this code to be able to close the explorer window from Silverlight LiteralControl JavaScript = new LiteralControl(); JavaScript.Text = @"<script type='text/javascript'> function doCloseLocal() { var version = parseFloat(navigator.appVersion.split('MSIE')[1]); if (version >= 7) {window.open('', '_parent', ''); } else { window.opener = self; } window.close(); } </script>"; this.Controls.Add(JavaScript); } #endregion }Fíjate también que antes de añadir el nuevo control hemos llamado al render de la clase base para que el web part se renderice
Y esta es la pinta que tiene el override en la vida real. Ahora sí que tiene sentido…
jueves, 12 de enero de 2012
Sobre Windows Phone 7 y la autenticación con SharePoint
Conectar el teléfono a nuestro entorno de SharePoint era mi proyecto de navidad, y me llevó un rato... Al final conseguí hacerlo así que he pensado en compartir mi alegría. Además, he leído un montón de blogs sobre el tema ¿todos los que hay? y quería aportar mi granito de arena.
Vamos al asunto. Me he creado una clase llamada SPAuthBridge partiend del código de la SDK que puede ser usada para realizar las conexiones con el servidor de SharePoint.
La manera de usar esto es bastante simple, creas el objeto SPAuthBridge y, llamas al método Authenticate y listo, algo así:
Es preciosocuando funciona…
Vamos al asunto. Me he creado una clase llamada SPAuthBridge partiend del código de la SDK que puede ser usada para realizar las conexiones con el servidor de SharePoint.
using System; using System.IO; using System.Net; using System.Text; namespace PhoneUtils.Code { public class SPAuthBridge { #region Properties public CookieContainer cookieJar = new CookieContainer(); string SiteUrl, User; string Password; //This should be securestring, but I don't think it's available in WP7 #endregion #region Constructors public SPAuthBridge(string SiteUrl, string User, string Password) { this.SiteUrl = SiteUrl; this.User = User; this.Password = Password; } #endregion #region Public Methods public void Authenticate() { try { if (string.IsNullOrEmpty(SiteUrl)) throw new ArgumentOutOfRangeException("The SPAuthBridge was not properly initialized"); System.Uri authServiceUri = new Uri(string.Format("{0}/_vti_bin/authentication.asmx", SiteUrl)); HttpWebRequest spAuthReq = HttpWebRequest.Create(authServiceUri) as HttpWebRequest; spAuthReq.CookieContainer = cookieJar; spAuthReq.Headers["SOAPAction"] = "http://schemas.microsoft.com/sharepoint/soap/Login"; spAuthReq.ContentType = "text/xml; charset=utf-8"; spAuthReq.Method = "POST"; //add the soap message to the request spAuthReq.BeginGetRequestStream(new AsyncCallback(spAuthReqCallBack), spAuthReq); } catch { TriggerOnAuthenticated(false); } } #endregion #region Private Methods private void spAuthReqCallBack(IAsyncResult asyncResult) { string envelope = @"<?xml version=""1.0"" encoding=""utf-8""?> <soap:Envelope xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/""> <soap:Body> <Login xmlns=""http://schemas.microsoft.com/sharepoint/soap/""> <username>{0}</username> <password>{1}</password> </Login> </soap:Body> </soap:Envelope>"; UTF8Encoding encoding = new UTF8Encoding(); HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState; Stream _body = request.EndGetRequestStream(asyncResult); envelope = string.Format(envelope, User, Password); byte[] formBytes = encoding.GetBytes(envelope); _body.Write(formBytes, 0, formBytes.Length); _body.Close(); request.BeginGetResponse(new AsyncCallback(ResponseCallback), request); } private void ResponseCallback(IAsyncResult asyncResult) { try { HttpWebRequest request = (HttpWebRequest)asyncResult.AsyncState; HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult); Stream content = response.GetResponseStream(); if (request != null && response != null) { if (response.StatusCode == HttpStatusCode.OK) { using (StreamReader reader = new StreamReader(content)) { //Put debugging code here string _responseString = reader.ReadToEnd(); reader.Close(); } } } //authentication complete TriggerOnAuthenticated(true); } catch { TriggerOnAuthenticated(false); } } #endregion #region Events public delegate void OnAuthenticatedHandler(bool Success); public event OnAuthenticatedHandler OnAuthenticated; protected virtual void TriggerOnAuthenticated(bool Success) { if (OnAuthenticated != null) OnAuthenticated(Success); } #endregion } }
La manera de usar esto es bastante simple, creas el objeto SPAuthBridge y, llamas al método Authenticate y listo, algo así:
StratexWP7SoapClient StratexWP7 = new StratexWP7SoapClient(); //This is my web service SPAuthBridge SharePointAuth; public MainPage() { InitializeComponent(); (...) SharePointAuth = new SPAuthBridge(SiteUrl, Username, Password); SharePointAuth.OnAuthenticated += new SPAuthBridge.OnAuthenticatedHandler(SharePointAuth_OnAuthenticated); if (!string.IsNullOrEmpty(Password)) SharePointAuth.Authenticate(); else MessageBox.Show("The application should be configured before use."); } void SharePointAuth_OnAuthenticated(bool Success) { if (!Success) { Deployment.Current.Dispatcher.BeginInvoke(() => { MessageBox.Show("There was an error on the authentication procedure. Please check the configuration."); }); return; } StratexWP7.CookieContainer = SharePointAuth.cookieJar; //This is all you have to do to connect your web service. \m/ O.O \m/ HookEvents(); RequestData(); }
Es precioso
(Por cierto, las gráficas son de AmCharts)
miércoles, 11 de enero de 2012
Las Barras de Desplazamiento aparecen en SharePoint 2010 en los Silverlights
Si, cada vez que fijas la altura o la anchura del web part te muestra las horribles barras de desplazamiento.
Esta no es la primera vez que trato me encuentro con esto, pero creo que esta vez lo he resuelto para siempreo por lo menos hasta la nueva versión de SharePoint el año que viene…. El problema estaba en la forma en que estaba creando el objeto Silverlight. Estaba usando estilos CSS en línea. Era algo así:
Después de un millón de experimentos basados en la técnica del ensayo / errorno pienses jamás que soy capáz de crear un Web Part Silverlight de los OOTB de SharePoint 2010 y copiar el código he cambiado la primera línea del objeto a:
Esta no es la primera vez que trato me encuentro con esto, pero creo que esta vez lo he resuelto para siempre
<object id='silverlightHost' style='height: 600px; width: 350px; margin: 0; padding: 0;' data='data:application/x-silverlight-2,' type='application/x-silverlight-2'>
Después de un millón de experimentos basados en la técnica del ensayo / error
<object id='silverlightHost' height='600px' width='350px' data='data:application/x-silverlight-2,' type='application/x-silverlight-2' style='display:block' class='ms-dlgDisable'>Y eso resuelve el problema.
viernes, 6 de enero de 2012
Creando Eventos en Silverlight
He leído que es imposible en un par de sitios. No se si lo sería en las versiones anteriores pero, por lo menos desde Silverlight 3 se puede… y digo más… es fácil.
Este evento lo uso para comprobar cuándo se ha movido un objeto.
Y entonces en la función en donde actualizo la posición del objeto llamo a:
Después de esto solo tengo que suscribirme al evento desde las otras clases:
Fácil…por una vez en la vida
Este evento lo uso para comprobar cuándo se ha movido un objeto.
#region Events public delegate void PositionChangeHandler(Object Sender); public event PositionChangeHandler PositionChangedEvent; protected virtual void OnPositionChanged() { if (PositionChangedEvent != null) PositionChangedEvent(this); } #endregion
Y entonces en la función en donde actualizo la posición del objeto llamo a:
OnPositionChanged();
Después de esto solo tengo que suscribirme al evento desde las otras clases:
ParentObjective.PositionChangedEvent += new Objective.PositionChangeHandler(RelatedObjectives_PositionChangedEvent);
Fácil…
miércoles, 4 de enero de 2012
Configurando Silverlight y Servicios Web con SSL y Certificados Autofirmados
Yo tenía un servidor de SharePoint funcionando perfectamente o por lo menos tan bien como funciona un server de SharePoint en HTTP, pero de repente sentí la urgencia de hacerlo HTTPS. Extendí la aplicación etc., y era capaz de acceder a todas partes, pero ¡Oh sorpresa!, mis silverlights no funcionaban.
Cambié el <security mode="Transport" /> en el ServiceReferences.ClientConfig pero siempre me daba el infame error:
Lo primero es añadir un buen certificado autofirmado a tu sitio.
El certificado se almacena en tus certificados personales, asi que para exportarlo deberás ejecutar MMC.exe, y allí:
Ahora podemos ir al server que queremos hacer SSL, si es que no estábamos allí ya, y copiar el fichero .pfxen algún sitio en donde luego recordemos donde esta.
Después abrimos el IIS Manager, seleccionamos el nodo raíz y hacemos click en Certificados del Servidor .
En la columna de la derecha deberás hacer click en Importar y seleccionar el fichero .pfx que acabas de crear. Después de importarlo deberás poder verlo en la lista de Certificados del Servidor.
Entonces haremos click en la colección de sitios 443 (la mía se llama SharePoint 443) y allí, otra vez en la columna derecha, hacemos click en bindings. Seleccionamos HTTPS (añade uno si no lo tienes ya creado) y luego hacemos click en editar. En la lista Certificado SSL selecciona el que acabas de subir.
Bien, ya hemos hecho la parte difícil, ahora todo es más fácil.
Ahora añadiremos el crossdomain.xml y el clientaccesspolicy.xml tanto a la carpeta 80 como a la 443. (c:\inetpub\…\443)
El contenido de estos ficheros es:
crossdomain.xml
clientaccesspolicy.xml
Y ahora la parte todavía más sencilla. Configurar el navegador.
Debemos navegar a https://MyURL y allí veremos algo así:
Aquí deberás hacer click en continuar (Recomendado) y después verás la barra de dirección volverse roja. Bien.
Haz click en el botón Error de Certificado y luego en el link Ver Certificados:
Después de eso:
Vamos bien. Ahora borraremos las cookies del navegadory todo lo demás, qué demonios:
Como toque final reiniciaremos el IIS y saltaremos tres veces a la pata coja.
Ahora, finalmente, debes de ser capaz de depurar tus web services SSL y tus Silverlights en Visual Studio.
Cambié el <security mode="Transport" /> en el ServiceReferences.ClientConfig pero siempre me daba el infame error:
An error occurred while trying to make a request to URI 'https://MyURL/_vti_bin/Service.asmx'. This could be due to attempting to access a service in a cross-domain way without a proper cross-domain policy in place, or a policy that is unsuitable for SOAP services. You may need to contact the owner of the service to publish a cross-domain policy file and to ensure it allows SOAP-related HTTP headers to be sent. This error may also be caused by using internal types in the web service proxy without using the InternalsVisibleToAttribute attribute.Hay un par de cosas que arreglar aquí.
Lo primero es añadir un buen certificado autofirmado a tu sitio.
C:\Program Files\Microsoft SDKs\Windows\v7.1>MakeCert.exe -r -pe -n "CN=MyURL" -sky exchange -ss myEl MyURL es muy importante. No puedes usar un certificado autofirmado cualquiera, debes usar uno que le venga bien a la URL de tu sitio.
El certificado se almacena en tus certificados personales, asi que para exportarlo deberás ejecutar MMC.exe, y allí:
File –> Add/Remove Snap-In –> Certificates –> My user account –> Finish –> OKEntonces podrás de ver tus certigicados. El que acabas de crear está enÑ:
Certificates – Current User –> Personal –> Certificates –> MyURLAllí deberás:
Click derecho –> All Tasks –> ExportY entonces asegurate de que exportas la clave privada con el certificado. Esto te dará como resultado un fichero .pfx.
Ahora podemos ir al server que queremos hacer SSL, si es que no estábamos allí ya, y copiar el fichero .pfx
Después abrimos el IIS Manager, seleccionamos el nodo raíz y hacemos click en Certificados del Servidor .
En la columna de la derecha deberás hacer click en Importar y seleccionar el fichero .pfx que acabas de crear. Después de importarlo deberás poder verlo en la lista de Certificados del Servidor.
Entonces haremos click en la colección de sitios 443 (la mía se llama SharePoint 443) y allí, otra vez en la columna derecha, hacemos click en bindings. Seleccionamos HTTPS (añade uno si no lo tienes ya creado) y luego hacemos click en editar. En la lista Certificado SSL selecciona el que acabas de subir.
Bien, ya hemos hecho la parte difícil, ahora todo es más fácil.
Ahora añadiremos el crossdomain.xml y el clientaccesspolicy.xml tanto a la carpeta 80 como a la 443. (c:\inetpub\…\443)
El contenido de estos ficheros es:
crossdomain.xml
<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <allow-access-from domain="*"/> <allow-http-request-headers-from domain="*" headers="SOAPAction"/> </cross-domain-policy>
clientaccesspolicy.xml
<?xml version="1.0" encoding="utf-8"?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="SOAPAction"> <domain uri="http://*"/> <domain uri="https://*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
Y ahora la parte todavía más sencilla. Configurar el navegador.
Debemos navegar a https://MyURL y allí veremos algo así:
Aquí deberás hacer click en continuar (Recomendado) y después verás la barra de dirección volverse roja. Bien.
Haz click en el botón Error de Certificado y luego en el link Ver Certificados:
Después de eso:
Instalar Certificado… –> Next –> & Next –> Finish –> Yes –> Ok.Cierra el navegador y ábrelo otra vez tu https://myurl. Ahora deberías ver la barra de la URL en blanco y el icono del candado al lado:
Vamos bien. Ahora borraremos las cookies del navegador
Como toque final reiniciaremos el IIS
Ahora, finalmente, debes de ser capaz de depurar tus web services SSL y tus Silverlights en Visual Studio.
Suscribirse a:
Entradas (Atom)
No hay comentarios:
Publicar un comentario