вторник, августа 21, 2012

Показываем иконки для Tortoise SVN x64 под Total Commander под Windows 7

Заметил, что Total Commander под Windows 7 x64 не отображает иконки для Tortoise SVN x64, решается путем настройки самого Total Commander: Tools -> Options -> Icons -> Show overlay icons - после этого жизнь становится значительно проще;



среда, июля 11, 2012

VirtualBox машина, доступная по WiFi снаружи (VirtualBox machine available outside using WiFi connection)

Данный пост - больше как заметка для себя, чтобы не забыть :)

Потребовалось расшарить VirtualBox машину таким образом, чтобы можно было к ней достучаться через WiFi, скажем, с iPad или с телефона. Необходимость возникла в удаленной отладке приложения на iPad, но на самом деле сценарии могут быть различные, вплоть до тестирования web-сайта в мобильных приложениях.

Моя конфигурация:
Хост  - Windows 7 SP1 64bit
Гостевая - Windows XP 


Итак, в моем случае порядок действий был следующий:
На хосте настроено wifi-подключение, чтобы посмотреть все доступные сетевые подключения, идем в
Network and Sharing Center

Выбираем наше беспроводное соединение, смотрим Details, тут важно обратить внимание, к какой сетевой карте осуществлена привязка, также видим ip-адрес нашего хоста - в принципе по данному адресу можно достучаться как снаружи к нашему хосту, так и из гостевой ОС (если только файрволл не режет ваши запросы).


Тот же самый IP мы можем увидеть, используя ipconfig и рассмотрев подключение Wireless LAN adapter.
Предыдущий шаг важен лишь для того, чтобы увидеть имя сетевой карты, которое мы будем использовать для настройки виртуальной машины.

Теперь самое главное - идем в VirtualBox Gui, выбираем нашу виртуальную машину и в ее настройках выбираем Network Adapters.
Выбираем мост, Bridged Adapter, и здесь важно выбрать ту же сетевую карту, которая использована для беспроводного сетевого подключения на хосте - в данном случае IP адрес для гостевой ОС будет выделен из того же диапозона, в котором и хостовая ОС, с той же самой маской подсети, то есть физически и хост и гостевая ОС будут находиться в одной беспроводной сети:


Запускаем виртуальную машину, смотрим новый присвоенный IP адрес (в моем случае это был ровно следующий выделенный адрес 104), убеждаемся, что и маска и шлюз подсети одни и теже, что и на хосте:



Потребуется настроить файрволл на гостевой ОС, в простейшем случае его можно просто отключить, и пробуем пропинговать нашу виртуалку, используя WiFi. В случае с iOS я установил бесплатную утилиту Free Ping, пингуем:


На этом все, надеюсь кому-то еще, кроме меня, пригодится данная инструкция.


четверг, марта 29, 2012

Верх насилия над собой (или как отпилить сук, на котором сидишь) - 2

А usability настройки сетевой карты так и осталось на уровне 5ти летней давности, никакого confirmation dialog при клике на Disable вы не получите :) ... просто так вот тут вспомнилось, на тему создания проблем самому себе любимому





пятница, ноября 11, 2011

iPad 2 Тихий звук в наушниках

Я профан в плане работы со всякого рода apple девайсами, но вот захотел посмотреть кино на iPad 2 и был разочарован совершенно неприемлемым тихим звуком в наушниках, причем самое странное то, что без наушников орет мама не горюй...

Уже подумал было, что возможен производственный брак (тем более, есть оказывается и у девайсов от такого известного производителя дефекты, о чем можно убедиться немного погуглив), как нарыл решение - оно оказалось простым и наверняка о нем знает каждый уверенный пользователь iPad:

Идем в настройки, раздел Музыка, видим там чекбокс на ограничение громкости, снимаем его и выставляем в слайдере максимальное значение, которое хотим - далее можно глохнуть в наушниках настолько насколько мы этого хотим :)

пятница, октября 07, 2011

Regex для распарсивания выражений с форматирующей маской {name: formatMask, formatLength}

Понадобилось недавно написать regex-выражение для распарсивания строк вида {name: formatMask, formatLength}, при этом форматирующая маска или длина могут присутствовать или отсутствовать.

Привожу код такого regex:


\{\s*(?<FieldExpression>\s*(?<FieldName>\w{1,}){1}\s*(?<FormatExpression>(\s*(?<ColonSeparator>[:])|(?<CommaSeparator>[,]))\s*(?(ColonSeparator)(?<FormatMask>\w*)|)[,]*\s?(?<Length>\d+)*)*)\s?\}


В группах имеем возможность получить сам fieldName, его маску fieldMask и длину length. Еще хочу отметить, что при работе с regex очень помогает такой инструмент как Rad Software Regular Expressions Designer -http://www.radsoftware.com.au/?from=RegexDesigner.



Пишем ajax available user control

Исходные коды к статье
Привет.


Сегодня хотелось бы поделиться одной вкусной штукой, которая может оказаться полезной во многих сценариях в работе с asp.net web forms, возможно, в SharePoint тоже окажется полезной.
Вкратце, суть идеи в следующем: хочется со стороны клиента, из клиентского сценария, вызвать серверный метод....но....естественно, все это делается через коллбеки и реализацию ICallbackEventHandler....хочется сделать это один раз для всех возможных сценариев и не думать каждый раз о реализации указанного интерфейса, думать о сериализации/десериализации аргументов и результатов от сервера в строку и прочими делами.... хочу просто тупо вызвать серверный метод по его имени, получить ответ, причем ответ хочу получить в объектном виде, не хочу получать строки и потом парсить их на стороне сервера. Вот такое желание. Чтобы сразу увидеть результат того, что получится, приведу в начале статьи кусочек кода.

Итак, объявляем серверный user control:


public partial class DemoUserControl : AjaxWebModule
    {
        #region demo methods here
        public AjaxResponse MyDemoMethod1(string arg1, string arg2, int arg3, bool arg4)
        {
            AjaxResponse result = new AjaxResponse
            {
                AdditionalSettings = String.Format("This is response to callback with parameters {0}/{1}/{2}/{3} from server side", arg1, arg2, arg3, arg4),
                CurrentPage = 0,
                Records = new List<object>() 
                {
                    new 
                    {
                        Date = DateTime.Now,
                        Data = int.MinValue
                    }
                },
                TotalRecords = 1,
                TotalPages = 1
            };

            return result;
        }

        public AjaxResponse MyDemoMethod2(int arg1, bool arg2)
        {
            AjaxResponse result = new AjaxResponse
            {
                AdditionalSettings = String.Format("This is response to callback with parameters {0}/{1} from server side", arg1, arg2),
                Records = new List<object>() 
                {
                    new 
                    {
                        Date = DateTime.Now,
                        Data = int.MinValue,
                        OtherProperties = new List<object>{1, 2, 3}
                    }
                }
            };

            return result;
        } 
        #endregion
    }
И его серверный маркап:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="DemoUserControl.ascx.cs" Inherits="AjaxAvailableUserControls.Application.AjaxUserControls.DemoUserControl" %>
<input type="button" onclick="click_handler();" value="click me to call server side method 1" id="btnDemo1"/><br />
<input type="button" onclick="click_handler();" value="click me to call server side method 2" id="btnDemo2"/><br />
<script language="javascript" type="text/javascript">
    
    $(document).ready(function()
    {
            $("#btnDemo1").click(function() 
            {
                jQuery.execute("MyDemoMethod1",
                { "arg1": "test string 1", "arg2": "test strign 2", "arg3": 2, "arg4": false },
                {
                    onSuccess: function (data) {
                        debugger;
                        alert("settings:" + data.AdditionalSettings + ";currentPage:" + data.CurrentPage.toString());
                    },
                    onError: function (exception) { alert(exception); }
                });
            });

            $("#btnDemo2").click(function () {
                jQuery.execute("MyDemoMethod2",
                { "arg1": 1, "arg2": false },
                {
                    onSuccess: function (data) {
                        debugger;
                        alert("settings:" + data.AdditionalSettings + ";currentPage:" + data.CurrentPage.toString());
                    },
                    onError: function (exception) { alert(exception); }
                });
            });

    });
</script>
Как видите, все достаточно просто. При разработке code-behind класса нашего .ascx контрола нам всего лишь нужно отнаследоваться от AjaxWebModule и объявить те методы, которые мы хотим использовать на стороне клиента.

При этом сохраняется сигнатура вызова метода со стороны клиента. Важно только то, что данный метод должен быть объявлен как public и должен возвращать результат в виде объекта класса AjaxResponse.  В вышеприведенном коде мы производим вызов двух серверных методов  - MyDemoMethod1 и MyDemoMethod2 - передача параметров происходит поименновано путем указания имени аргумента и его значения:

{ "arg1": "test string 1", "arg2": "test strign 2", "arg3": 2, "arg4": false }  - (string arg1, string arg2, int arg3, bool arg4)


{ "arg1": 1, "arg2": false } - (int arg1, bool arg2)

Подробнее о классе AjaxResponse немного попозже, его интерфейс  может быть изменен вами, для данного примера он разработан, чтобы отвечать наиболее общему варианту использования в таблицах с постраничным пейджингом.

Итак, в вышеприведенном клиентском сценарии я умышленно оставил вставки debugger, чтобы продемонстировать в конечном итоге результат.

Кликаем по первой кнопке:




Как видно, со стороны клиента мы вызываем серверный метод MyDemoMetho1 и получаем результат в объектном виде, без всяких преобразований - магия :), более того, мы имеем возможность обработать как нормальное выполнение метода, так и его ошибку в случае какой-либо исключительной ситуации.

Вызов со стороны клиента подробнее:

jQuery.execute("MyDemoMethod1",
                { "arg1": "test string 1", "arg2": "test strign 2", "arg3": 2, "arg4": false },
                {
                    onSuccess: function (data) {
                        debugger;
                        alert("settings:" + data.AdditionalSettings + ";currentPage:" + data.CurrentPage.toString());
                    },
                    onError: function (exception) { alert(exception); }
                });


Как видите, в данном случае  вызов серверного метод обернут в jquery-плагин .execute, код которого доступен в исходном коде, сделано это исключительно просто ради удобства.

Результат вызова мы уже видели на скриншоте выше, вот что мы получим на клиентской стороне в javascript путем обращения к переменной data в onSuccess обработчике:









Как видно, результат нормально десериализован, мы имеем на руках как обычные свойства объекта data, так и коллекцию вложенных объектов Records.

Кликаем по второй кнопке, в принципе здесь картина ничем не отличается, просто возвращаемый результат немного посложнее, значение переменной data в onSuccess обработчике будет выглядеть так:




Собственно, все, надеюсь вам понравилась идея? Далее идет объяснение некоторых моментов.


Итак, в первую очередь нам важно, как реализован класс AjaxWebModule, именно он предоставляет возможность вызова серверного метода со стороны клиента. 


Итак, его сигнатура:


///
/// Base class for all modules contained ajax logic
///
public class AjaxWebModule : UserControl, ICallbackEventHandler
{
..................
}
Здесь все знакомо, данный класс реализует интерфейс ICallbackEventHandler, подробнее о нем останавливаться не буду, о нем уже писал достаточно подробно ранее. Важно, что в реализации текущего интефейса мы можем получить со стороны клиента строку, распарсить ее, выполнить действия, и вернуть строковый результат клиенту. Собственно, само распарсивание строки запроса выполняется в методе:



/// <summary>
        /// Обрабатывает запрос со стороны клиента, вызывает соотвествующий метод из текущего модуля, формирует ответ, json-сериализует его в строку и
        /// отправляет обратно на клиента
        /// </summary>
        /// <param name="e"></param>
        private void ProcessRequest(ClientDataReceivedEventArgs e)
        {
            e.Cancel = false;
            //  Данные со стороны клиента, передаваемые в качестве параметров в наш ajax метод
            //  Параметры должны соотвествовать структуре класса AjaxRequestParameters
            //  e.ClientData

            AjaxRequestParameters parameters = this.ParseParameters(e.ClientData);


            //  вызов серверного ajax-метода
            AjaxResponse response = this.MethodInvoke(parameters);

            //  Сериализованное состояние ответа от сервера, отправляемое клиенту в качестве ответа
            //  e.ServerResponse
            //  формируем ответ клиенту
            JavaScriptSerializer serializer = new JavaScriptSerializer();
            e.ServerResponse = serializer.Serialize(response);
            
        }

Вот здесь мы и встречаем наш AjaxResponse - объект данного класса мы получаем от метода MethodInvoke, который принимает объект класса AjaxRequestParameters,  полученный путем распарсивания строки со стороны клиента. 


Мы пока рассматриваем только серверную логику, до клиентской еще дойдем. Класс AjaxRequestParameters представляет собой следующее:

/// <summary>
    /// Параметры, передаваемый в ajax метод со стороны клиента
    /// </summary>
    public class AjaxRequestParameters
    {

        /// <summary>
        /// Имя вызываемого метода
        /// </summary>
        public string MethodName
        {
            get;
            set;
        }

        /// <summary>
        /// Список ЗНАЧЕНИЙ параметров, которые должны соотвествовать соотвествующим аргументам указанного метода - передаются со стороны клиента в виед пар Ключ-Значение
        /// </summary>
        public Dictionary<string, object> Parameters
        {
            get;
            set;
        }
    }



Здесь мы уже видим название метода, и список параметров/значений, которые будут переданы на выполнение указанному методу. Надеюсь, уже становится понятным, что делает метод MethodInvoke(AjaxRequestParameters parameters) - его задача просто найти указанный метод в текущем объекте юзер-контрола, вызвать его и обернуть результат выполнения в заданный формат. 


Код его приводить наверно нет смысла, он использует рефлексию для поиска метода с заданной сигнатурой, важно то,что возвращаемый результат искомого метода должен быть AjaxResponse


Почему нам важна сигнатура возвращаемого результата? Только потому, что нам придется данный результат передавать на клиента, а чтобы его передать, нам нужно его сериализовать в строку, потому ответ от сервера должен отвечать определенным требованиям, а именно, он должен отвечать правилам сериализации. В данном примере я просто использовал простые типы данных для объвления интерфейса AjaxResponse, вы можете использовать другой интерфейс.

Итак, мы дошли до вызова серверного метода, получения результата его выполнения. Далее этот результат сериализуется в строку и уходит на клиента:



//  Сериализованное состояние ответа от сервера, отправляемое клиенту в качестве ответа
//  e.ServerResponse
//  формируем ответ клиенту
JavaScriptSerializer serializer = new JavaScriptSerializer();
e.ServerResponse = serializer.Serialize(response);



По серверному коду вопросов остаться не должно, теперь можно рассмотреть клиентскую реализацию. Сразу приведу исходный код jquery плагина - execute:



//  core. client scripts library
//  created on 20111007 by smirnov andrey  - duШes
//  #region execute extension method
$.execute = function (methodName, parameters, options) {
    /// <summary>
    ///  Вызывает серверный public-Метод текущего AjaxWebModule
    /// </summary>
    /// <param name="methodName" type="Object">
    ///  Аргумент methodName представляет собой имя вызываемого серверного метода, например,
    ///     "DemoMethod2"
    /// </param>
    /// <param name="parameters" type="Object">
    ///  Объект parameters представляет собой хеш с указанием списка параметров в виде хеш - Имя параметра - Значение, например, 
    ///     { "id", 1 }, { "name", "test" }
    /// </param>
    /// <param name="options" type="Object">
    ///  Объект options представляет собой хеш с указанием OnSuccess handler в случае успешного завершения вызова и OnErrorHandler в случае ошибки, например
    ///  {
    ///              onSuccess: function(data) {
    ///                  alert(deserialized_request.AdditionalSettings);
    ///              }
    ///              onError: function(data) {
    ///                  alert("Exception thrown");
    ///              }
    /// </param>
    /// <returns type="undefined">
    var settings = jQuery.extend({
        control_id: null,
        onSuccess: function (data) { },
        onError: function (data) { alert("Exception thrown ->" + data); }
    }, options || {});

    var localOnSuccessHandler = function (data) {
        /// <summary>
        ///  Данная функция является callback функцией, которая вызывается в случае успешного завершения внутреннего вызова ajaxRequest
        /// </summary>
        /// <param name="data" type="String">
        ///  Результат в виде строки - сериализованное состояние объекта-ответа со стороны серверной части
        /// </param>
        /// <returns type="undefined" />
        var deserialized_request = $.JSON.decode(data);
        settings.onSuccess(deserialized_request);

    }

    var localOnErrorHandler = function (exception) {
        /// <summary>
        ///  Данная функция является callback функцией, которая вызывается в случае НЕуспешного завершения внутреннего вызова ajaxRequest
        /// </summary>
        /// <param name="data" type="String">
        ///  Результат ошибки в виде строки
        /// </param>
        /// <returns type="undefined" />
        settings.onError(exception);
    }

    var arrayParameters = [];
    $.each(parameters, function (name, value) {
        var wrapperParameter =
        { "Key": name,
            "Value": value
        };
        arrayParameters.push(wrapperParameter);
    });
    var request = {
        MethodName: methodName,
        Parameters: arrayParameters
    };

    ajaxRequest(settings.control_id, request, localOnSuccessHandler, localOnErrorHandler);
}
//  #endregion


Что мы здесь видим. Первое - settings для нашего плагина, как нам рекоментует делать jQuery Framework.
http://docs.jquery.com/Plugins/Authoring#DefaultsandOptions


var settings = jQuery.extend({
 control_id: null,
 onSuccess: function (data) { },
 onError: function (data) { alert("Exception thrown ->" + data); }
 }, options || {});

Далее, локальный обработчик успешного вызова Callback со стороны клиента, как видим, здесь локальный обработчик делегирует вызов нашему обработчику onSuccess из settings, предварительно десериализовав результат:



var localOnSuccessHandler = function (data) {

 ///    <summary>
 ///        Данная функция является callback функцией, которая вызывается в случае успешного завершения внутреннего вызова ajaxRequest
 ///    </summary>
 ///    <param name="data" type="String">
 ///        Результат в виде строки - сериализованное состояние объекта-ответа со стороны серверной части
 ///    </param>
 ///    <returns type="undefined" />
 var deserialized_request = $.JSON.decode(data);
 settings.onSuccess(deserialized_request);

}
Аналогично, локальный обработчик неуспешного вызова Callback со стороны клиента, здесь десериализовать ничего не нужно, нам нужно получить только сообщение об ошибке со стороны сервера.



var localOnErrorHandler = function (exception) {
  ///    <summary>
  ///        Данная функция является callback функцией, которая вызывается в случае НЕуспешного завершения внутреннего вызова ajaxRequest
  ///    </summary>
  ///    <param name="data" type="String">
  ///        Результат ошибки в виде строки
  ///    </param>
  ///    <returns type="undefined" />
  settings.onError(exception);
}


Теперь уже осталось немного, мы дошли до подготовки наших данных для отправки на сервер, здесь подгатавливаем объект request, в котором мы указали имя метода и список его параметров. 


Помните класс AjaxRequestParameters на серверной стороне?  да, это именно его представление, в объект класса AjaxRequestParameters может быть преобразован данный объект со стороны клиента.



var arrayParameters = [];
 $.each(parameters, function (name, value) {
 var wrapperParameter =
 { "Key": name,
  "Value": value
 };
 arrayParameters.push(wrapperParameter);
});

var request = {
 MethodName: methodName,
 Parameters: arrayParameters
};



Все готово, вызываем ajaxRequest:




ajaxRequest(settings.control_id, request, localOnSuccessHandler, localOnErrorHandler);


Стоп, что это !!! что это за ajaxRequest такой?!!

Не волнуйтесь, ajaxRequest - это само ядро callback вызова. Где оно формируется? 
А формируется оно в уже рассмотренном нами классе AjaxWebModule:

/// <summary>
        /// Имя функции, которая будет использоваться для вызова серверного сценария со стороны клиента, принимает один аргумент ARG в виде строки 
        /// </summary>
        [Browsable(true)]
        [Category("Callback Handlers")]
        [DefaultValue("serverCall")]
        [Description("Имя функции, которая будет использоваться для вызова серверного сценария со стороны клиента, принимает один аргумент ARG в виде строки ")]
        public string ServerCallFunctionName
        {
            get
            {
                return "ajaxRequest";
            }
        }



        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            #region регистрация server callback function со стороны клиента - здесь формируем wrapper на функцией WebFormdoCallback

            if (!this.Page.ClientScript.IsClientScriptBlockRegistered(this.Page.GetType(), "AjaxRequestWebFormDoCallbackScript"))
            {
                string webFormDoCallbackScript = this.Page.ClientScript.GetCallbackEventReference("control_id", "serialized_request", "localOnSuccessHandler", null, "localOnErrorHandler", true);
                string serverCallScript = "function " + this.ServerCallFunctionName + "(control_id, arg, localOnSuccessHandler, localOnErrorHandler){" +
                    "\r\n" +
                    "var serialized_request = $.JSON.encode(arg);\r\n" +
                    "if (typeof(control_id) == 'undefined' || control_id == null)\r\n" +
                    String.Format("{{ control_id='{0}';}}\r\n", this.UniqueID) +
                    webFormDoCallbackScript +
                    ";\n}\n";

                this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), "AjaxRequestWebFormDoCallbackScript", serverCallScript, true);
            }
            #endregion

            #region WebformDoCallback script registration

            //  fix проблемы описанной http://www.codeproject.com/KB/aspnet/pendingcallbacks.aspx

            //  регистрируем функцию WebForm_CallbackComplete_SyncFixed, в которой нет ошибки с обращение к переменной i в цикле for
            string callbackCompleteFixScriptName = "WebForm_CallbackComplete_SyncFixed";
            string callbackCompleteFixScript = @"
            function WebForm_CallbackComplete_SyncFixed() {
              // the var statement ensure the variable is not global
                 for (var i = 0; i < __pendingCallbacks.length; i++) {
                    callbackObject = __pendingCallbacks[i];
                    if (callbackObject && callbackObject.xmlRequest && 
               (callbackObject.xmlRequest.readyState == 4)) {
                        
                        if (!__pendingCallbacks[i].async) { 
                            __synchronousCallBackIndex = -1;
                        }
                        __pendingCallbacks[i] = null;
                        var callbackFrameID = '__CALLBACKFRAME' + i;
                        var xmlRequestFrame = document.getElementById(callbackFrameID);
                        if (xmlRequestFrame) {
                            xmlRequestFrame.parentNode.removeChild(xmlRequestFrame);
                        }
                        WebForm_ExecuteCallback(callbackObject);
                    }
                }
            }
            ";
            if (!this.Page.ClientScript.IsClientScriptBlockRegistered(this.Page.GetType(), callbackCompleteFixScriptName))
            {
                this.Page.ClientScript.RegisterClientScriptBlock(this.Page.GetType(), callbackCompleteFixScriptName, callbackCompleteFixScript, true);
            }



            //  заменяем функцию WebForm_CallbackComplete на нашу WebForm_CallbackComplete_SyncFixed и регистрируем ее на момент
            //  полной загрузки страницы
            string onloadScriptName = "pageload_callback_complete_fix";
            string onloadScript = @"
            if (typeof (WebForm_CallbackComplete) == 'function') {
                WebForm_CallbackComplete = WebForm_CallbackComplete_SyncFixed;
            }
            ";

            if (!this.Page.ClientScript.IsStartupScriptRegistered(callbackCompleteFixScriptName))
            {
                this.Page.ClientScript.RegisterStartupScript(this.GetType(), onloadScriptName, onloadScript, true);
            } 
            #endregion
        }




Если мы посмотрим исходный код нашей страницы, то увидим результат выполнения OnPrerender:



function ajaxRequest(control_id, arg, localOnSuccessHandler, localOnErrorHandler){
 var serialized_request = $.JSON.encode(arg);
 if (typeof(control_id) == 'undefined' || control_id == null)
 { control_id='ctl00$Content$DemoUserControl1';}
  WebForm_DoCallback(control_id,serialized_request,localOnSuccessHandler,null,localOnErrorHandler,true);
 }




Собственно, здесь как раз и происходит "заворачивание" нашего "объектного" вызова в строку и передача его в WebForm_DoCallback


Сигнатура данного метода подробно описана как в msdn, так и у меня в статьях по выполнения cakkback со стороны клиента ранее. 
Надеюсь, магии и не рассмотренных вопросов тут не осталось, вызов со стороны клиента по прежнему остался прежним, реализуется он через ICallbackEventHandler


Передача параметров туда и обратно осталось такой же - а  именно  - только путем передачи строковых значений.


Мы же просто обернули все это в красивую оболочку, которой будет приятно пользоваться, но понимание того, как это все работает, важно.  Вы можете расширить возможности данного подхода, например, для поддержки какого-то jQuery ui-контрола, например, грида и прочее.

Чуть не забыл....если вы внимательно посмотрели на код реализации ajaxRequest не смогли не заметить такой параметр, как controlid (по умолчанию его значение null). Зачем оно? 


Нужен этот параметр только для того, чтобы разделить вызовы из разных user controls, т.е. в том случае, когда на странице есть несколько user controls, из клиенских скриптов которых осуществляется вызов серверных методов, причем, сигнатура их может быть совпадать. Или, в том случае, когда на странице два инстанса нашего  ajax available user control - тут нам и пригодится controlid, чтобы разделить вызовы, идущие к конкретному инстансу user control. Делается это так:



jQuery.execute("DemoMethod1", { "id": 1, "name": "testName" },
 {
  control_id: '<%= UniqueID %>',
  onSuccess: function(data) {
   alert("settings:" + data.AdditionalSettings + ";currentPage:" + data.CurrentPage.toString());
  },
  onError: function(exception) { alert(exception); }
 });

Спасибо за то, что осилили так "много букав", надеюсь статья будет полезной в использовании. В начале статьи приведена ссылка на пример данного решения.

среда, сентября 28, 2011

SharePoint 2010 unit testing in Visual Studio 2010

Бля ну что за лажа с шарпоинт, точнее даже не с ним :) (не кидайте камнями, решение внизу)

Вдруг выяснилось, что нельзя написать ms test targeted to .net 3.5 в Visual Studio 2010, чтобы написать парочку тестов, связанных с sharepoint 2010.

В конечном итоге так и придется использовать какие-нить тулзы типа nUnit...

Ладно, в любом случае в процессе гуглинга надыбал интересную фишку, а именно, все же можно заставить ms test выполняться в x64 mode.

Подробности здесь
http://msdn.microsoft.com/en-us/library/ee782531.aspx

что удивительно, описание в картинках, вот так бы почаще :)

Достучаться до этого диалога можно, находясь в контексте ms тестового проекта:

 


Как выяснилось, решение все же есть:

 

http://msdn.microsoft.com/en-us/library/gg601487.aspx

Testing SharePoint 2010 Applications

The capabilities listed above also enable you to write unit tests and integration tests for SharePoint 2010 applications using Visual Studio 2010 Service Pack 1. For more information about how to develop SharePoint 2010 applications using Visual Studio 2010, see SharePoint Development in Visual Studio, Building and Debugging SharePoint Solutions and Verifying and Debugging SharePoint Code by Using ALM Features.

Limitations

The following limitations apply when you re-target your test projects to use the .NET Framework 3.5:

In the .NET Framework 3.5, multitargeting is supported for test projects that contain only unit tests. The .NET Framework 3.5 does not support any other test type, such as coded UI or load test. The re-targeting is blocked for test types other than unit tests.

Execution of .NET Framework 3.5 tests is supported only in the default host adapter. It is not supported in the ASP.NET host adapter. ASP.NET applications that have to run in the ASP.NET Development Server context must be compatible with the .NET Framework 4.

Data collection support is disabled when you run tests that support .NET Framework 3.5 multitargeting. You can run code coverage by using the Visual Studio command-line tools.

Unit tests that use .NET Framework 3.5 cannot run on a remote machine. 

 

В описании сервис-пака:
http://support.microsoft.com/kb/983509

Basic Unit Testing support for the .NET Framework 3.5
In Visual Studio 2010 SP1, you now have the functionality to test your applications that target the .NET 

Итак, поехали, сервиспак радостно качаем отсюда:

http://www.microsoft.com/download/en/confirmation.aspx?id=23691

Теперь выставляем targetting framwework в .net 3.5, хост выполнения теста в x64 mode:

Наслаждаемся:


Будут вопросы, пишите :) Будет время, отвечу :)

 

 

 


 


четверг, июля 21, 2011

пятница, июля 08, 2011

[Window Server 2008, SharePoint 2010, Visual Studio, TFS] How to connect to TFS with different user credentials

Собственно, потребовалось подключиться к TFS под другим аккаунтом, оказалось, это достаточно проблематично, если сама студия уже сохранила user credentials.

Никакого диалога в студии вы не найдете, попытки disconnect в team viewer и обратный connect ничего не дадут, вы будете работать под предыдущим аккаунтом.

Проблема рассматривалась также вот здесь:

Connect to TFS with different user credentials

но мне как-то это не сильно помогло, итак мой рецепт, как это лечить:

 

1. Идем в control panel, фильтруем по manage passwords:

 

2. Далее, manage windows credentials:

Здесь видим тот аккаунт, который windows будет использовать по умолчанию для доступа к TFS:

 

Делаем Remove from vault, перегружаем студию, в следующей попытке сделать connect to TFS студия запросит новый аккаунт.

Кеширование аккаунта происходит по той причине, что мы сами его и сохраняем, когда выбираем чекбокс Remember my credentials:

 

 

среда, марта 31, 2010

Тесты и Exception вида "System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used"....

В проекте используется com interrop,вкратце, создается COM-инстанс и обращение к нему происходит через singletone-объект;

Необходимо было покрыть тестами часть данной функциональности, связанной с обращением к COM, и вот тут-то при запуске нескольких тестов и стало возникать исключение:

"System.Runtime.InteropServices.InvalidComObjectException: COM object that has been separated from its underlying RCW cannot be used"

Проблема связана с тем, что каждый тест по умолчанию запускается в отдельном потоке, или даже домене (подробнее не разбирался), и доступ к инстансу COM-server происходил из другого потока...

Проблему пофиксил пока путем настройки не паралельного запуска тестов, а их последовательного выполнения. 

Настраивается такое поведение путем внесения изменений в Local.testsettings файл, а именно, путем указания количества одновременно выполняемых тестов в 



<Execution parallelTestCount=""<вставить нужное="" значение="">




  если parallelTestCount == 0, то тесты выполняются параллельно, количество тестов в пуле выбирается самой студией...



  Мне же был нужен следующий вариант:

  <TestSettings name="Local" id="38863d1e-30b7-4b4a-a629-4add84f4982e" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
    <Description>These are default test settings for a local test run.</Description>
    <Execution parallelTestCount="1">
      <TestTypeSpecific />
      <Timeouts runTimeout="900000" testTimeout="900000" />
      <ExecutionThread apartmentState="MTA"/>
      <AgentRule name="Execution Agents">
      </AgentRule>
    </Execution>
  </TestSettings>


Важно: чтобы ваши изменения вступили в силу, нужно перегрузить проект/решение;

ps: попутно читал следующие ресурсы:

    http://connect.microsoft.com/VisualStudio/feedback/details/534124/exception-running-tests-that-use-waithandle-waitall-waithandles

    VS2010 tip: How to run unit tests in parallel
    http://blogs.microsoft.co.il/blogs/dhelper/archive/2010/03/02/vs2010-tip-how-to-run-unit-tests-in-parallel.aspx

    Parallel Test Execution in Visual Studio 2010
    http://msmvps.com/blogs/p3net/pages/parallel-test-execution-in-visual-studio-2010.aspx

    Executing Unit Tests in parallel on a multi-CPU/core machine
    http://blogs.msdn.com/vstsqualitytools/archive/2009/12/01/executing-unit-tests-in-parallel-on-a-multi-cpu-core-machine.aspx