Gdzie Visual Studio przechowuje informacje o ostatnio używanych projektach (MRU List)? Odpowiedź jest prosta: w rejestrze systemu Windows.
Dokładniej jest to: HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\ProjectMRUList
Blog Dariusza Tarczyńskiego: ASP.NET, C#, MVC, TDD, inżynieria oprogramowania
Gdzie Visual Studio przechowuje informacje o ostatnio używanych projektach (MRU List)? Odpowiedź jest prosta: w rejestrze systemu Windows.
Dokładniej jest to: HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\ProjectMRUList
Kolejne spotkanie Poznańskiej Grupy .NET jest już za nami. Jak zwyklę postaram się przedstawić krótką relację, dla tych, który na spotkaniu być nie mogli.
Pierwsze spotkanie było w moim wykonaniu, dlatego nie będę go oceniał. Fakt, jest taki, że chyba do speaker idola dużo mi brakuje :-) Uświadomił mnie o tym swoim wystąpieniem Mariusz Jarzębowski.
Tematem prezentacji Mariusza była "Historia wstążki". Jak dla mnie kolejne zaskoczenie na plus (bo nie spodziewałem się rocket science). Świetnie przedstawiona ewolucja interfejsów spod stajni Microsoftu. Ogromna pasja przekazana na zewnątrz, ciekawe historie, których próżno szukać w internecie i niezwykle ciekawa osoba Mariusza sprawiły, że nikt na sali nie ziewał :-) Dużo ciekawostek, które wyszly "przy okazji". No i pełen profesjonalizm w przekazywaniu tych wszystkich nowinek. Mała kawka po spotkaniu i wymiana paru zdań z kolegami z grupy, a potem... oczekiwanie na kolejne spotkanie!
Zapraszam wsyzstkich na 14 sporkanie Poznańskiej Grupy .NET. Gościem specjalnym będzie Mariusz Jarzębowski, ktory opowie o histrii wstążki (The Storry of the Ribbon).
No a drugim gościem będe ja :-) Opowiem, jak podejść do tworzenia nowoczesnych aplikacji ASP .NET, czyli niezbędny toolbox: JavaScript, ASP .NET Ajax, itp.
Start: 17:45 w siedzibie firmy Cognifide
Jest kilka sposobów na odczytywanie dokumentów tekstowych i wyszukiwanie w nich określonych treści. Często najbardziej niezawodne jest użycie do tego celu wyrażeń regularnych, czyli inaczej mówiąc regexa. Oto przykład jak pobrać tytuł witryny:
public string GetHTMLPageTitle(string file){Match m = Regex.Match(file, @"<title>\s*(.+?)\s*</title>");if (m.Success)return m.Groups[1].Value;elsereturn string.Empty;}
Kto nie był, niech żałuje. Po pierwsze gratulacje dla zespołu Cognifide za pokaz ich (chyba sztandarowego) dzieła: EpiServera CMS. O ile byłem troszkę sceptycznie nastawiony do prezentacji, gdyż nie liczyłem na nic odkrywczego, to po prezentacji byłem zaskoczony na plus! Bardzo dobry przykład wykorzystania technologii .NET w stworzeniu naprawdę dużego narzędzia bijącego moim zdaniem Sharepointa na głowę. Kilka mechanizmów EpiServer mnie nawet zainspirowało - przedewszystkim ten rozszerzalny framework.
Inauguracja PLSSUG i prezentacja Stefana rozwiała przede wszystkim mój niedawny problem. Druga sprawa, to fajnie jest być na bieżąco jeśli chodzi o SQL Servera. Prezentacja również jak najbardziej udana. Stefan to niezły geek sqlowy :-). Taaak rozumiem jak to jest kiedy się przychodzi do domu, a tu nikt nie wie o czym się mówi :-)
Podsumowując: kolejny udany wieczór z kodem :-)

Mam przyjemność zaprosić wszystkich programistów i nie-programistów na imprezę Microsoft IT Academic Days, która odbędzie się 4 grudnia w Centrum Wykładowym Politechniki Poznańskiej. ITAD to cykl akademickich wykładów na których można zobaczyć i usłyszeć wiele ciekawych informacji o technologiach i innowacyjnych rozwiązaniach firmy Microsoft, ale nie tylko. Będę miał na niej swoje "pięć" minut przedstawiając wykład pt. "Trzej muszkieterowie, czli ASP .NET MVC". Start imprezy o godzinie 9:00. Dostępna jest już pełna Agenda. Zachęcam wszystkich gorąco, mimo że zimno :-) !
Wstęp jest bezpłatny, po wcześniejszej rejestracji na portalu CodeGuru. Strona konferencji: http://itacademicday2008.studentlive.pl/
Operacje na datach to pewnie chleb powszedni dla każdego programisty. Wstawianie ich do bazy danych, to kolejna, seryjna nasza czynność. W zasadzie bardziej chodzi mi o umieszczanie domyślnych wartości daty w tabelach bazy danych MS SQL Server. W sumie niebyłoby nic odkrywczego, gdyby nie fakt, że wyjątki w takim kodzie pojawiają się w najmniej oczekiwanych momentach :-)
Istnieje spora różnica między wartościami: DateTime.MinValue, który w rezultacie da: 01-01-0001, a SqlDateTime.MinValue, który zwróci: 01-01-1753. Cała zabawa powoduję, że podczas próby wstawienia rekordu, którego wartość w kolumnie typu DateTime jest generowana w kodzie programu przez DateTime.MinValue otrzymujemy wyjątek przekroczenia wartości. Dlatego, aby wstawić minimalną wartość typu DateTime do bazy danych należy zawsze posługiwać się SqlDateTime.MinValue.
Jeszcze ciekawsza systuacja jest przy porównaniu wartości: DateTime.MaxValue oraz SqlDateTime.MaxValue. Zróbmy proste zadanie: wstawmy do tabeli w naszej bazie danych wartość DateTime.MaxValue, potem odczytajmy tą wartość do zmiennej lokalnej tego samego typu. Na szybko naskrobałem coś takiego:
static void Main(string[] args){DateTime fromDb = DateTime.MaxValue;DateTime toDb = DateTime.MaxValue;SqlConnection conn = new SqlConnection(@"Data Source=TOSHIBA\SQLEXPRESS;Initial Catalog=Poligon;Integrated Security=True");try{conn.Open();SqlCommand cmd = new SqlCommand("INSERT INTO Daty VALUES(@data)", conn);cmd.Parameters.Add("data", System.Data.SqlDbType.DateTime).Value = DateTime.MaxValue;cmd.ExecuteNonQuery();SqlCommand getCmd = new SqlCommand("SELECT TOP(1) DAT_Data FROM Daty", conn);using (IDataReader rdr = getCmd.ExecuteReader()){while(rdr.Read()){fromDb = Convert.ToDateTime(rdr["DAT_Data"]);}}bool areSame = (DateTime.Compare(toDb, fromDb) == 0);TimeSpan subDates = toDb.Subtract(fromDb);int result = subDates.Milliseconds; //nasz wynik}finally{conn.Close();conn.Dispose();}}
Że co?
Metoda DateTime.Compare nie zwróciła nam wartości 0 (zero), co oznaczałoby, że data zapisana do bazy i ta odczytana są takie same. Zwróciła nam wartość, która oświadcza nam, że wartość otrzymana z bazy danych jest mniejsza od tej, którą zapisaliśmy.
Zaglądamy do zmiennej result...
Zmienna result trzyma dla nas różnicę obu dat. Po odjęciu wartości otrzymanej z bazy danych od tej którą zapisywaliśmy dostajemy różnicę wynoszącą... dwie równiutkie milisekundy.
Prawie robi wielką różnicę
DateTime.MaxValue to: 31-12-9999 23:59:59.999, natomiast maksymalna wartość daty dla bazy danych, czyli to co zwraca SqlDateTime.MaxValue to: 31-12-9999 23:59:59.997 Różnica dwóch milisekund występuje w momencie konwersji DateTime do SqlDateTime, tu nie otrzymujemy wyjątku - a powinniśmy gdyż zakresy nieznacznie, bo nieznacznie, ale różnią się. Baza danych posłusznie przyjmuje wartość potajemnie ucinając 2 ms. Biednemu developerowi wydaje się że wszystko jest ok (bo narzędzia podglądu danych oraz debugger nie wyświetlają wartości z tak dużą precyzją). Pytanie za 100 punktów: co powodują taką różnice? Dlaczego nie można było tej wartości wydłużyć o te 2 nieszczęsne milisekundy?
NLog - projekt o polskich korzeniach!
Tak, tak - nie tylko prezydent USA ma polskie korzenie! Ma je także NLog, gdyż został napisany przez polskiego programistę: Jarka Kowalewskiego. Ale nie dlatego chcę przedstawić tą bibliotekę jako wartą wypróbowania. Jest to biblioteka, która z pewnością dostarczy nam wszystkich niezbędnych mechanizmów potrzebnych do logowania zdarzeń w naszych aplikacjach. Niestety aktualnie autor nie uczestniczy tak aktywnie w projekcie jak kiedyś.
Zanim przystąpimy do implementacji gorąco zachęcam do zapoznania się z poprzednim artykułem poświęconym log4net, a także wstępniakiem do całej serii Wystarczy, że jako czytelnik poznasz zasadę rejestracji zdarzeń w log4net, gdyż w NLog jest ona analogiczna.
Zaczynamy przygodę z NLog
Pierwsze co musimy zrobić, to ściągnąć binarki z oficjalnej strony projektu. Ja do celów tego artykułu wykorzystam wersję nlog-1.0-setup.exe. Dzięki tej paczce oprócz samych plików dll otrzymamy także dokumentację i zbiór przykładów. Po instalacji dodajemy referencję do NLog.dll znajdującym się w folderze net-2.0. Następnie do projektu dodajemy plik konfiguracyjny NLog.config - plik ten zostanie automatycznie wczytany i NLog z niego pobierze sobie konfigurację. Ustawmy mu właściwość "Copy to Output Directory" na: "Copy always". Wyjście naszych logów - o priorytetach: Debug wzwyż - będziemy kierować na konsolę, dlatego do pliku konfiguracyjnego powinniśmy dodać wpis: "targets".
<?xml version="1.0" ?><nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><targets><target name="console" xsi:type="Console"layout="${longdate}|${level}|${message}"/></targets></nlog>
Cel mamy zdefiniowany, teraz pora na definicję zasad: tak jak ustaliliśmy wcześniej interesuje nas jedynie logowanie zdarzeń o priorytecie Debug i wyższych. Dodajmy więc definicję rules poniżej definicji targets:
<rules><logger name="*" minlevel="Debug" writeTo="console" /></rules>
Sama zasada wstawiania zdarzeń do logów jest praktycznie identyczna jak w log4net. Pierwsze co musimy zrobić to otrzymać instancję klasy Logger. Robimy to wywołując statyczną metodę klasy LoggerManager:
using System;using NLog;namespace NLogSample{class Program{private static Logger logger = LogManager.GetCurrentClassLogger();static void Main(string[] args){logger.Debug("Nasz pierwszy komunikat NLog!");}}}
Po testowym uruchomieniu efekt powinien być podobny do poniższego:
Ok, ale logowanie na konsolę to na pewno nie jest to, co docelowo chcielibyśmy osiągnąć. Spróbujmy zatem tym razem logować zdarzenia do pliku tekstowego: chyba jednej z czytelniejszej formy logowania zdarzeń (choć problem z filtrowaniem ich to inna sprawa). Aby nie niszczyć pracy jaką włożyliśmy w poprzedni przykład :-) nasza aplikacja do pliku tekstowego będzie logowała zdarzenia o priorytetach Error i wyższych, reszta niech idzie na konsolę, tak jak było to dotychczas. Dodajmy więc nową regułę do pliku konfiguracyjnego NLog.config:
<logger name="*" minlevel="Error" writeTo="file" />
Czas na dodanie wpisu konfiguracyjnego samego celu:
<target name="file" xsi:type="File"layout="${longdate} ${logger} ${message}"fileName="${basedir}/logfile.txt"keepFileOpen="false"encoding="iso-8859-2" />
Myślę, że poszczególne atrybuty są na tyle czytelne, że nie trzeba ich szerzej opisywać, ciekawskich odsyłam do dokumentacji :-) Po modyfikacjach plik NLog.config powinien wyglądać następująco:
<?xml version="1.0" ?><nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><targets><target name="console" xsi:type="Console"layout="${longdate}|${level}|${message}"/><target name="file" xsi:type="File"layout="${longdate} ${logger} ${message}"fileName="${basedir}/logfile.txt"keepFileOpen="false"encoding="iso-8859-2" /></targets><rules><logger name="*" minlevel="Debug" writeTo="console" /><logger name="*" minlevel="Error" writeTo="file" /></rules></nlog>
Czas na odrobinę kodu, przypominam dodajemy logowanie zdarzeń typu Error do pliku tekstowego:
using System;using NLog;namespace NLogSample{class Program{private static Logger logger = LogManager.GetCurrentClassLogger();static void Main(string[] args){logger.Debug("Nasz pierwszy komunikat NLog!");int a = 1;int b = 0;try{int c = a / b;}catch (Exception ex){logger.Error(ex);}}}}
Wystarczy nam teraz uruchomić aplikację i przekonać się, że w folderze ze skompilowanym programem został utworzony plik logu, w którym będą lądowały wyjątki przechwycone w blokach try-catch.
Oczywiście to dopiero ułamek potęgi biblioteki NLog. Oprócz jej niewątpliwej szybkości (sprawdzonej w kilku benchmarkach), należy odnotować mnogość celów dla zdarzeń, czyli:
• Class Description
• ASPNetTraceTarget
• ChainsawTarget
• ConsoleTarget
• DatabaseParameterInfo
• DatabaseParameterInfoCollection
• DatabaseTarget
• DebuggerTarget
• DebugTarget
• FileTarget
• FormControlTarget
• MailTarget
• MemoryTarget
• MessageBoxTarget
• MethodCallParameter
• MethodCallParameterCollection
• MethodCallTarget
• NetworkTarget
• NLogViewerTarget
• NullTarget
• RichTextBoxTarget
• TraceTarget
• WebServiceTarget
Jako zadanie domowe mogę zachęcić Was do zalogowania zdarzeń do kilku innych wyjść.

using System;
using System.Collections.Generic;
using log4net;
using log4net.Config;
namespace ConsoleApplication1
{
class Program
{
private static readonly ILog MyLogger = LogManager.GetLogger(typeof(Program));
static void Main(string[] args)
{
BasicConfigurator.Configure(); //konfiguracja podstawowa, bez pliku xml
MyLogger.Info("Aplikacja została uruchomiona");
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
</layout>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
</configuration>
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
</configSections>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
</configuration>
namespace ConsoleApplication1
{
class Program
{
private static readonly ILog MyLogger = LogManager.GetLogger(typeof(Program));
static void Main(string[] args)
{
XmlConfigurator.Configure();
MyLogger.Info("Aplikacja została uruchomiona");
If (args != null)
MyLogger.Debug(string.Format("Argumenty uruchomieniowe: {0}", args));
MyLogger.Info("Aplikacja została zakończona");
}
}
}






<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Panel ID="pnlSurvey" runat="server">
<asp:Label ID="lblQuestion" runat="server" />
<asp:Repeater ID="rptAnswers" runat="server"
onitemdatabound="rptAnswers_ItemDataBound">
<HeaderTemplate>
<h3>Możliwe odpowiedzi:</h3>
</HeaderTemplate>
<ItemTemplate>
<asp:CheckBox ID="chckAnswer" runat="server" /><br />
</ItemTemplate>
</asp:Repeater>
</asp:Panel>
<asp:Button ID="btnNext" runat="server" Text="Dalej >>" onclick="btnNext_Click"
Visible="False" />
<br /> <asp:HyperLink ID="HyperLink1" runat="server"
NavigateUrl="Default.aspx?QuestionNo=0">Początek ankiety</asp:HyperLink>
</div>
</form>
</body>
</html>
using System;
using System.Configuration;
using System.Web.UI.WebControls;
public partial class _Default : System.Web.UI.Page
{
public string[] Questions
{
get
{
if (ConfigurationManager.AppSettings["Questions"] != null)
{
return ConfigurationManager.AppSettings["Questions"].ToString().Split(';');
}
return new string[] { };
}
}
public int ActualQuestionNo
{
get
{
if (Request.QueryString["QuestionNo"] != null)
return Convert.ToInt32(Request.QueryString["QuestionNo"]);
return -1;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (ActualQuestionNo >= 0)
{
PrepareAnswers(PrepareQuestion());
btnNext.Visible = true;
}
}
private void PrepareAnswers(int p)
{
if (ConfigurationManager.AppSettings["Answers"] != null)
{
string[] answers = ConfigurationManager.AppSettings["Answers"].ToString().Split(';');
if (p < answers.Length)
{
string[] anwersQ = answers[p].Split(',');
if (anwersQ.Length >= 1)
{
rptAnswers.DataSource = anwersQ;
rptAnswers.DataBind();
}
}
}
}
private int PrepareQuestion()
{
int questionNo = ActualQuestionNo;
if (questionNo >=0)
{
if (questionNo <= Questions.Length -1 )
lblQuestion.Text = Questions[questionNo];
}
return questionNo;
}
protected void rptAnswers_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
CheckBox chck = e.Item.FindControl("chckAnswer") as CheckBox;
if (chck != null)
chck.Text = e.Item.DataItem as string;
}
}
protected void btnNext_Click(object sender, EventArgs e)
{
if (ActualQuestionNo >= 0)
{
if (ActualQuestionNo < Questions.Length - 1)
{
//Zachowaj Odpowiedz
int toQuestion = ActualQuestionNo + 1;
Response.Redirect(string.Format("Default.aspx?QuestionNo={0}", toQuestion));
}
else
Response.Redirect("SurveyEnd.aspx");
}
}
}
<appSettings>
<add key="Questions" value="Jakie masz ulubione danie?; Jakie lubisz marki samochodów?; Twoje zainteresowania"/>
<add key="Answers" value="Pizza, Gołąbki, Schabowy; Alfa Romeo, Audi, Fiat, Porshe; IT, Film, Muzyka"/>
</appSettings>
using System;
namespace MockingTaxiService
{
interface ITaxiService
{
bool Order(DateTime doDate, string toAddress, int clientID);
}
}
Prosta klasa klienta:
using System;
namespace MockingTaxiService
{
public class TaxiService : ITaxiService
{
#region ITaxiService Members
public bool Order(DateTime doDate, string toAddress, int clientID)
{
System.Threading.Thread.Sleep(5000);
return true;
}
#endregion
}
}
using System;
namespace MockingTaxiService
{
public class TaxiClient
{
public bool OrderTaxiTooday(string toAddress, int clientID)
{
TaxiService svc = new TaxiService();
return svc.Order(DateTime.Now, toAddress, clientID);
}
}
}
using System;
using MbUnit.Framework;
namespace MockingTaxiService.Tests
{
[TestFixture]
class SmokeTest
{
[Test]
public void TestShouldReturnTrue()
{
TaxiService svc = new TaxiService();
bool result = svc.Order(DateTime.Now.AddHours(5), "ul. Nieznana 12, Poznań", 1234);
// w przyp. sukcesu wynik powinien być typu: true
Assert.IsTrue(result);
}
}
}

using System;
using MbUnit.Framework;
using Rhino.Mocks;
namespace MockingTaxiService.Tests
{
[TestFixture]
class SmokeTest
{
MockRepository _mocks;
[SetUp]
public void initialize()
{
_mocks = new MockRepository();
}
[Test]
public void TestShouldReturnTrue()
{
var taxiService = _mocks.DynamicMock<ITaxiService>();
using (_mocks.Record())
{
SetupResult.For(taxiService.Order(DateTime.MinValue, null, 0)).IgnoreArguments().Return(true);
}
using (_mocks.Playback())
{
Assert.IsTrue(taxiService.Order(DateTime.Now, "ul. Nieznana 12, Poznań", 123));
}
}
}
}
var taxiService = _mocks.DynamicMock<ITaxiService>();
using (_mocks.Record())
{
SetupResult.For(taxiService.Order(DateTime.MinValue, null, 0)).IgnoreArguments().Return(true);
}
using (_mocks.Playback())
{
Assert.IsTrue(taxiService.Order(DateTime.Now, "ul. Nieznana 12, Poznań", 123));
}
public class TaxiService : ITaxiServicezanim złoży odpowiednią dyspozycję w systemie musi sprawdzić: dostępność taksówki, czas: czy jest możliwe przybycie auta na wybraną godzinę, odnaleźć dane klienta na podstawie jego numeru ID oraz sto innych rzeczy. Proces złożenia dyspozycji w samym systemie trwa np. ok 40 sekund, a jego wynikiem wartość boolowska.
{
public bool OrderTaxi(DateTime toDateTime, string toAddress, int clientID)
{
//logika biznesowa
}
}