http://www.thomasmaurer.ch/2011/07/office-365-how-to-connect-with-powershell/
http://www.thomasmaurer.ch/2011/10/change-office-365-password-expiration-policy/
| 2012-04-04 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
| 2012-03-24 | Posted by Mateusz Świetlicki under MVC, Polskie blogi IT, Twórczość |
|
Nie jestem pewien czy to w ogóle dobra praktyka, ale ubzdurałem sobie ostatnio, że fajnie mieć akcję kontrolera która będzie wyświetlać wiadomości w stylu “Sukces, wróć do strony głównej”.
Mimo tego, że może niekoniecznie jest to dobry pomysł to nauczyłem się trochę starając się to napisać, a to opis mojego rozwiązania:
Na początek w kontrolerze Home dodałem akcję:
public ActionResult MessageBox(string title, string body)
{
ViewBag.MsgTitle = title;
ViewBag.MsgBody = body;
return View();
}
Którą wywołując to przez:
RedirectToAction("MessageBox",
"Home",
new RouteValueDictionary()
{
{"title", "Tytuł"},
{"body", "Wiadomość."}
});
Z tym rozwiązaniem są jednak 2 problemy. Po pierwsze w tej formie title i body wysyłane są w adresie metodą GET, po drugie wywołanie wykorzystuje chronioną metodę RedirectToAction klasy Controller:
protected internal RedirectToRouteResult RedirectToAction(string actionName, string controllerName);
Przez co nie można użyć jej w statycznej funkcji w jakichś Utils’ach.
Rozwiążemy te problemy po kolei. Do przesłania title i body zamiast RouteValueDictionary możemy użyć ControllerBase.TempData które pozwalają na przesłanie danych do następnego zapytania. Uzyskamy wtedy:
/// <summary>
/// Place title into TempData["MsgTitle"]
/// Place body into TempData["MsgBody"]
/// </summary>
/// <returns></returns>
public ActionResult MessageBox()
{
ViewBag.MsgTitle = TempData["MsgTitle"].ToString();
ViewBag.MsgBody = TempData["MsgBody"].ToString();
return View();
}
i
TempData["MsgTitle"] = title;
TempData["MsgBody"] = body;
return RedirectToAction("MessageBox", "Home");
Problem nr 2 (protected RedirectToAction) pewnie można rozwiązać na kilka sposobów. Moja pierwszą próbą było użycie:
HttpContext.Response.Redirect("~/Home/MessageBox");
Niestety użycie Response.Redirect nie jest kompatybilne z TempData. Dlatego zdecydowałem się użyć starego i trochę nieprzewidywalnego tricku z użyciem Refleksji. Otóż metody prywatne wciąż mogą być wywołane z dowolnego miejsca, trzeba jedynie użyć parametrów BindingFlags.NonPublic i BindingFlags.Instance metody Type.GetMethod. Ostatecznie powstało więc takie zręczne rozszerzenie:
public static class ControllerExtensions
{
public static ActionResult RedirectToMessageBox(this Controller controller, string title, string body)
{
controller.TempData["MsgTitle"] = title;
controller.TempData["MsgBody"] = body;
var redirect = controller.GetType().GetMethod("RedirectToAction",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new Type[] { typeof(string), typeof(string) },
null);
return redirect.Invoke(controller, new object[] { "MessageBox", "Home" }) as ActionResult;
}
}
Teraz mogę już przekierować przekierować do ekranu MessageBox z dowolnego kontrolera prostą metodą:
return this.RedirectToMessageBox("Tytuł", "Wiadomość.");
Pozdrawiam.
| 2012-03-22 | Posted by Mateusz Świetlicki under Dobre praktyki, Entity Framework, Polskie blogi IT |
|
Piszą aplikację webową z użyciem Entity Framework prędzej czy później natrafi się na problem z ilością instancji naszego DbContext.
Prawie wszystkie przykłady pokazują aby robić po prostu:
MyDbContext db = new MyDbContext();
W każdym kontrolerze. Co działa, ale szybko okazuje się problematyczne gdy chcemy tworzyć funkcje wspólne dla wielu kontrolerów albo gdy chcemy wykonywać biznesowe operacje wewnątrz modelu.
Weźmy na przykład statyczną metodę Create dla obiektu modelu (którą z jakiegoś powodu chcielibyśmy napisać):
class House
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Window> Windows { get; set; }
public House()
{
Windows = new List<Window>();
}
public static House Create()
{
var house = new House() { Name = "Domek" };
var db = new MyContext();
db.Houses.Add(house);
db.SaveChanges();
return house;
}
}
Jeśli spróbowalibyście teraz wykorzystać obiekt klasy House zwrócony przez tą funkcję okaże się wyskakują dziwne błędy np.:
MyContext context = new MyContext(); var house = House.Create(); context.Houses.Remove(house); context.Dispose();
Zakończy się błędem:
The object cannot be deleted because it was not found in the ObjectStateManager.
Jest to związane z tym, że nie wolno współdzielić obiektów między instancjami DbContext. Więc pierwszą myślą mogło by być “Stwórzmy pojedynczą Singletonową instancję MyContext!”. Tak, tak to nie też nie jest mądre.
class MyContext: DbContext
{
static MyContext _singleton;
public static MyContext Singleton
{
get { return _singleton ?? (_singleton = new MyContext()); }
}
public DbSet<House> Houses { get; set; }
public DbSet<Window> Windows { get; set; }
}
Podejście Singleton będzie działać i rozwiąże wcześniejsze problemy, ale po chwili spotkamy się z jeszcze trudniejszymi problemami jak znikające dane, nieodświerzające się kolekcje, wielowątkowe blokady czy błędy przy SaveChanges().
Najlepszym rozwiązaniem do jakiego ja doszedłem jest tworzenie 1 instancji na 1 wątek/HttpContext/Jednostę pracy.
private static PasDb _pasDb;
public static void SetStaticCurrent(PasDb current)
{
_pasDb = current;
}
public static PasDb Current
{
get
{
if (HttpContext.Current == null)
return _pasDb ?? (_pasDb = new PasDb());
if (HttpContext.Current.Items.Contains("db"))
return HttpContext.Current.Items["db"] as PasDb;
return (HttpContext.Current.Items["db"] = new PasDb()) as PasDb;
}
}
protected override void Dispose(bool disposing)
{
if (_pasDb == this)
_pasDb = null;
else if (HttpContext.Current.Items.Contains("db"))
HttpContext.Current.Items.Remove("db");
base.Dispose(disposing);
}
statyczne _PasDb jest przydatny dla testów i w szczególnych wypadkach.
PS:
To z jakiegoś powodu wydało mi się powiązane z tematem ;P
| 2012-03-21 | Posted by Mateusz Świetlicki under Entity Framework, Polskie blogi IT |
|
Standardowym zachowaniem EntityFramework CodeFirst jest brak akcji w przypadku usunięcia obiektu powiązanego relacją jeden-do-wielu. Powoduje pozostawienie wpisów z Null w foreign key.
Oto przykład:
class House
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Window> Windows { get; set; }
public House()
{
Windows = new List<Window>();
}
}
class Window
{
[Key]
public int Id { get; set; }
public int Size { get; set; }
public virtual House House { get; set; }
}
class MyContext: DbContext
{
public DbSet<House> Houses { get; set; }
public DbSet<Window> Windows { get; set; }
}
class Program
{
static void PrintHousesAndWindows(MyContext context)
{
Console.WriteLine();
Console.WriteLine("Number of houses: " + context.Houses.Count());
Console.WriteLine("Number of windows: " + context.Windows.Count());
Console.WriteLine();
}
static void Main(string[] args)
{
Console.WriteLine("Start");
MyContext context = new MyContext();
PrintHousesAndWindows(context);
Console.WriteLine("Adding house...");
var house = new House() { Name = "My house" };
context.Houses.Add(house);
Console.WriteLine("Adding windows...");
house.Windows.Add(new Window() { Size = 150 });
house.Windows.Add(new Window() { Size = 200 });
Console.WriteLine("Saving...");
context.SaveChanges();
PrintHousesAndWindows(context);
Console.WriteLine("Removing house...");
context.Houses.Remove(house);
context.SaveChanges();
PrintHousesAndWindows(context);
Console.WriteLine("Finish");
Console.ReadLine();
}
}
Wynik programu jest następujący:
Start Number of houses: 0 Number of windows: 0 Adding house... Adding windows... Saving... Number of houses: 1 Number of windows: 2 Removing house... Number of houses: 0 Number of windows: 2 Finish
A gdy spojrzymy w bazę danych okaże się, że House_Id w wierszach tabeli Windows jest równy NULL.
Id Size House_Id 1 150 NULL 2 200 NULL
Aby wymusić usuniecie obiektów klasy Windows z bazy wystarczy dodać atrybut Required przez właściwością House klasy Window.
class Window
{
[Key]
public int Id { get; set; }
public int Size { get; set; }
[Required]
public virtual House House { get; set; }
}
Oczywiście po zmianie modelu przez uruchomieniem programu musimy usunąć potrzednie wpisy i wywołać automatyczną migrację
update-database -force
Więcej o CodeFirst Migrations tutaj
Teraz wynik programu wygląda następująco:
Start Number of houses: 0 Number of windows: 0 Adding house... Adding windows... Saving... Number of houses: 1 Number of windows: 2 Removing house... Number of houses: 0 Number of windows: 0 Finish
Jak widzimy usunięcie House usunęło także Windows.
Jest jeszcze możliwość kontroli cascade delete przy użyciu FluidAPI lub Convention nawet na opcjonalnych parametrach.
| 2012-03-20 | Posted by Mateusz Świetlicki under Polskie blogi IT, Twórczość, Web scraping |
|
Podstawowa obsługa protokołu http i pobieranie treści stron internetowych w .NET jest bardzo prosta. Wystarczy użyć obiektu klasy WebClient. Tradycyjnie:
var WC = new WebClient();
string source = WC.DownloadString("http://www.codeguru.pl");
Lub asynchronicznie (WP7 czy C# 4.5):
var WC = new WebClient();
string source = await WC.DownloadStringAsync("http://www.codeguru.pl");
Niestety, WebClient zadziała tylko gdy używamy metody “GET”, czyli wpisujemy jedynie adres strony. Nie pozwala np. na przesłanie formularza logowani,a które najczęściej wymagają metodę “POST”. Brakuje też kontroli ciasteczek, headerów i możliwości własnej obsługą kodów błędów.
Dlatego też użyjemy bardziej zaawansowanej klasy HttpWebRequest. Oto prosty przykład:
using System.Net;
using System.IO;
...
string source = DownloadWebPageString("http://www.codeguru.pl");
...
private static string DownloadWebPageString(string url)
{
CookieContainer Cookies = new CookieContainer();
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(url);
Request.Accept = "*";
Request.Method = "GET";
Request.CookieContainer = Cookies;
Request.KeepAlive = true;
Request.UserAgent = "Opera/9.80 (Windows NT 6.1; U; pl)";
using (var Response = (HttpWebResponse)Request.GetResponse())
{
using (var SR = new StreamReader(Response.GetResponseStream()))
{
return SR.ReadToEnd();
}
}
}
Oczywiście podany kod nie obsługuje metody “POST” i brakuje mu obsługi błędów, dlatego też zamieszczę trochę pełniejszą klasę którą kiedyś (dawno temu) napisałem w tym celu:
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
public class HTTPRequest
{
public HTTPRequest()
{
Cookies = new CookieContainer();
}
public CookieContainer Cookies;
private List<FormInput> ReqInput = new List<FormInput>();
private List<FormInput> ReqHeader = new List<FormInput>();
public string OpenSite(string URL, RequestENUM ReqType, string ContentType)
{
String response;
Encoding enc = Encoding.Default;
try
{
Stream s = OpenSiteToSR(URL, ReqType, ContentType);
MemoryStream ms = new MemoryStream(); //TODO: Możę da się to zoptymlizować
{
int bytes = 0;
byte[] temp = new byte[4096];
while ((bytes = s.Read(temp, 0, temp.Length)) != 0)
ms.Write(temp, 0, bytes);
temp = new byte[512];
ms.Position = 0;
ms.Read(temp, 0, 512);
ms.Position = 0;
string str = enc.GetString(temp);
Match m = new Regex("charset=(?<ENC>.+?)[\"|>]").Match(str);
if (m.Success) enc = Encoding.GetEncoding(m.Groups["ENC"].Value);
}
StreamReader SR = new StreamReader(ms, enc);
response = SR.ReadToEnd();
SR.Close();
}
catch (Exception e)
{
response = e.Message;
}
return response;
}
public Stream OpenSiteToSR(string URL, RequestENUM ReqType, string ContentType)
{
//Thread.CurrentThread.Name = "HTTPRequest event";
string RequestSTR = GETRequest();
if (ReqType == RequestENUM.HTTP_GET)
{
URL = URL + "?" + RequestSTR;
}
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(URL);
HttpWebResponse Response;
Stream SR;
StreamWriter SW;
// Prepare Request Object
Request.Accept = "*";
Request.Method = ReqType.ToString().Substring(5);
Request.CookieContainer = Cookies;
// Set form/post content-type if necessary
if ((ReqType == RequestENUM.HTTP_POST && ReqInput.Count != 0 && string.IsNullOrEmpty(ContentType)))
{
ContentType = "application/x-www-form-URLencoded";
}
// Set Content-Type
Request.KeepAlive = true;
Request.UserAgent = "Opera/9.80 (Windows NT 6.1; U; pl)";
if ((!string.IsNullOrEmpty(ContentType)))
{
Request.ContentType = ContentType;
Request.ContentLength = RequestSTR.Length;
}
// Send Request, If Request
if ((ReqType == RequestENUM.HTTP_POST))
{
try
{
SW = new StreamWriter(Request.GetRequestStream());
SW.Write(RequestSTR);
SW.Close();
}
catch (Exception Ex)
{
throw Ex;
}
}
// Receive Response
try
{
Response = (HttpWebResponse)Request.GetResponse();
foreach (var item in Response.Headers)
{
string s = Response.GetResponseHeader(item.ToString());
}
SR = Response.GetResponseStream();
}
catch (Exception Ex)
{
throw Ex;
}
ClearInput();
return SR;
}
public void AddInput(FormInput input)
{
ReqInput.Add(input);
}
public void AddInput(string name, string value)
{
AddInput(new FormInput(name.Trim(), value.Trim()));
}
public void AddHeader(string name, string value)
{
ReqHeader.Add(new FormInput(name.Trim(), value.Trim()));
}
public void ClearInput()
{
ReqInput.Clear();
ReqHeader.Clear();
}
public string BildHtmlSite(string URL, RequestENUM ReqType)
{
StringBuilder SB = new StringBuilder();
SB.AppendLine("<HTML>");
SB.AppendLine("<body onLoad=\"document.formularz.submit();\">");
SB.AppendLine("<form name=\"formularz\" action=\"" + URL + "\" method=\"" + ReqType.ToString().Substring(5) + "\">");
int i = 0;
for (i = 0; i < ReqInput.Count; i++)
{
SB.AppendLine("<input name=\"" + ReqInput[i].Name + "\" type=\"text\" value=\"" + ReqInput[i].Value + "\" />");
SB.AppendLine("<br>");
}
SB.AppendLine("<input type=\"submit\" value=\"wyslij\" />");
SB.AppendLine("</form>");
SB.AppendLine("</body>");
SB.AppendLine("</HTML>");
return SB.ToString();
}
public string GETRequest()
{
StringBuilder SB = new StringBuilder();
int i = 0;
for (i = 0; i < ReqInput.Count; i++)
{
if (ReqInput[i].Name != string.Empty)
{
SB.Append(URLEncode(ReqInput[i].Name));
SB.Append("=");
SB.Append(URLEncode(ReqInput[i].Value));
if (i != ReqInput.Count - 1) SB.Append("&");
}
}
return SB.ToString();
}
private string URLEncode(string str)
{
return Uri.EscapeDataString(str);
}
private string UrlDecode(string str)
{
return Uri.UnescapeDataString(str);
}
public static List<FormInput> GetInputs(string source)
{
List<FormInput> lista = new List<FormInput>();
Regex rinput = new Regex("<input.+name=\"(?<name>[^\"]+).*?>");
Regex rinputvalue = new Regex("<input.+name=\"(?<name>[^\"]+).*?(value=\"(?<value>[^\"]*)).*?>");
MatchCollection ms = rinput.Matches(source);
foreach (Match item in ms)
{
FormInput i = null;
Match m = rinputvalue.Match(item.ToString());
if (m.Success)
{
i = new FormInput(m.Groups["name"].ToString(), m.Groups["value"].ToString());
}
else
{
i = new FormInput(item.Groups["name"].ToString(), "");
}
lista.Add(i);
}
return lista;
}
}
public enum RequestENUM
{
HTTP_GET = 0,
HTTP_POST = 1
}
public class FormInput
{
public string Name { get; set; }
public string Value { get; set; }
public FormInput() { }
public FormInput(string name, string value)
{
Name = name;
Value = value;
}
public override string ToString()
{
return string.Format("{0} = {1}", Name, Value);
}
}
Polecam przeanalizowanie kodu i działania klas WebHttpRequest i WebHttpResponse.
Prawdopodobnie teraz trochę bym w tym kodzie poprawił, ale działa i na potrzeby tego kursu spokojnie wystarczy (Najwyżej będziemy coś łatać).
W następnym odcinku przedstawię kilka sposobów jak wyszukiwać interesujących nas informacji w całym śmietniku HTMLa.
| 2012-03-19 | Posted by Mateusz Świetlicki under MVC, Polskie blogi IT |
|
Globalizacja jest ważną częścią nowoczesnych portali internetowych (choć osobiście robiłbym wszystko po angielsku i się nie przejmował). Niestety w MVC 3 nie obędzie się bez problemów.
Oto moje rozwiązanie:
Dane językowe trzymamy w plikach:
/App_GlobalResources/Translation.resx /App_GlobalResources/Translation.pl-PL.resx /App_GlobalResources/Translation.en-US.resx ...
Pliki dodajemy zwyczajnie:
Niestety plik ten nie będzie działać dobrze z System.ComponentModel.DataAnnotations:
[Display(ResourceType = typeof(Translation), Name = "RadarDataTimeLabel")]
public DateTime DateTime { get; set; }
Wyskoczy przy uruchomieniu wyjątek:
CS0122: 'Translation' is inaccessible due to its protection level
Lub podobny o tym że ‘Translation’ nie jest publiczny.
Translation is not public or does not contain a public static string property with the name RadarDataTimeLabel
Naprawić można to wykorzystując trick z http://holyhoehle.wordpress.com/2010/02/20/making-global-resources-public czyli zamieniając Custom Tool na PublicResXFileGenerator i Build Action na Embedded Resource.
Po tym jednak możemy się spodziewać wyjątku:
The type 'translation' exists in both ”XXX.dll“ and ”YYY.dll
Z tym poradzimy sobie zmieniając Namespace z ‘Resources’ na np. ‘MyProject.Web’.
Teraz aby wybrać konkretny język należy dodać wpis w web.config
<system.web>
<globalization uiCulture="auto" culture="auto" />
...
Albo określić język w kodzie przez
Translation.Culture = new CultureInfo("pl-PL");
| 2012-03-16 | Posted by Mateusz Świetlicki under Polskie blogi IT, Twórczość |
|
CodeFirst Migrations added new extension method to DbSet<TEntity> “AddOrUpdate”. Unfortunatly this method is useles if you using identity key with identity auto increment.
I created AddOrUpdateByProperty method which solve this bug and allows You to specify property from comparision:
public static class DbSet
{
public static void AddOrUpdateByProperty<TEntity>(this DbSet<TEntity> dbSet,
string propertyName, params TEntity[] entities) where TEntity : class
{
foreach (dynamic entity in entities)
{
var propertyValue = typeof (TEntity).GetProperty(propertyName)
.GetValue(entity, null) as object;
var oldEntity = dbSet.AddEqualityCondition(propertyName, propertyValue).FirstOrDefault();
if (oldEntity != null)
{ //Update
}
else
{ //Add
dbSet.Add(entity as TEntity);
}
}
}
public static IQueryable<T> AddEqualityCondition<T, V>(this IQueryable<T> queryable,
string propertyName, V propertyValue)
{
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
IQueryable<T> x = queryable.Where<T>(
Expression.Lambda<Func<T, bool>>(
Expression.Equal(Expression.Property(pe, typeof(T).GetProperty(propertyName)),
Expression.Constant(propertyValue, typeof(V)),
false,
typeof(T).GetMethod("op_Equality")),
new ParameterExpression[] { pe }));
return (x);
}
}
Hope You will find it useful :)
| 2012-03-15 | Posted by Mateusz Świetlicki under Polskie blogi IT, Twórczość |
|
Aby rozpocząć prace nad stworzeniem web scrapera potrzebne będzie nam seria narzędzi:
Wszystkie przykłady będę pisałem w języku C# 4.5 Beta chociaż większość zadziała także w C# 4.0.
OK, Gdy mamy już przygotowane środowisko jedyne co potrzebujemy to rozpoznanie celu dla naszego scrapera. Ja lubię zacząć od wypisanie adresów stron które mnie interesując oraz struktury formularzy które umożliwią mi wysyłanie informacji do portalu.
Adresy kopiujemy z pola adresu przeglądarki przeglądają portal:
http://www.codeguru.pl/kalendarium https://www.codeguru.pl/logowanie http://www.codeguru.pl/kalendarium/z-twoich-grup#eventTabs http://www.codeguru.pl/kalendarium/podglad-wydarzenia/wprowadzenie-do-wcf,6354 http://tools.codeguru.pl/Logon.aspx http://tools.codeguru.pl/system/lista-grup http://tools.codeguru.pl/system/edycja-grupy/67 http://tools.codeguru.pl/system/edycja-grupy/Events/67 http://tools.codeguru.pl/system/edycja-grupy/Members/67 http://tools.codeguru.pl/system/edycja-grupy/67/edycja-wydarzenia/Info/6354 http://tools.codeguru.pl/system/edycja-grupy/67/edycja-wydarzenia/Users/6354
Następnie potrzebuje szablonów formularzy. Najłatwiej pobrać je Fiddlerem o ile możemy zmusić stronę do działa w http nie w https. Jeśli formularz wymusza https jego strukturę musimy wyczytać ręcznie z htmla strony.
I tak np. formularz logowania do http://tools.codeguru.pl/Logon.aspx odczytany fiddlerem wymaga następujących pól:
__EVENTTARGET __EVENTARGUMENT __VIEWSTATE __EVENTVALIDATION ctl00$cphMain$txtUserLogin ctl00$cphMain$txtUserPassword ctl00$cphMain$btnUserLogin
Po uruchomieniu fiddlera i upewnienia się że tryb przechwytywania jest uruchomionym, wchodzimy na stronę logowania, logujemy się, znajdujemy na liście fiddlera drugie (powrotne) odwołanie do “tools.codeguru.pl /Logon.aspx”.
70 302 HTTP tools.codeguru.pl /Logon.aspx 7 435 private, no-cache="Set-Cookie" text/html; charset=utf-8 iexplore:3480
Następnie po prawej wybieramy zakładkę Inspectors/WebForms i odczytuje Body formularza.
Ciąg dalszy nastąpi…
| 2012-03-12 | Posted by Mateusz Świetlicki under Polskie blogi IT, Twórczość |
|
Myślę, że będzie to seria artykułów opisująca jak zrobić pasywny i aktywny web scraper, czyli aplikację która ściąga dane ze stron internetowych udając człowieka.
Aplikacje tego typu są bardzo przydatne gdy chcemy rozwinąć możliwości stron internetowych, przeanalizować ich zawartość, stworzyć własne bazy danych czy automatyzować pewne procesy np.: dodawanie artykułu w wielu miejscach jednym kliknięciem.
Web scraping jest konieczny ponieważ większość stron internetowych nie udostępnia API programistycznego chociaż są wyjątki np.: Facebook, twitter, LastFM mają swoje API które są zazwyczaj wystarczające, choć facebookowe na przykład jest trochę ograniczone.
Przez kolejne artykuły przejdziemy przez proces stworzenia naszego własnego API do portalu CodeGuru.pl
Portal z powodów finansowych nie ma żadnego poprawnego interfejsu programistycznego. Co swoją stroną jest głupie bo przecież nie jest to takie pracochłonne, a przynosi spore ułatwienia dla zaawansowanych użytkowników. W powodów praktycznych tworząc API skupimy się wydarzeniach oraz administracji grupy. Pomoże to grupom .NET w całej Polsce.
OK, To tyle słowem wstępu.
Spodziewajcie się kolejnych artykułów w niedalekiej przyszłości.
| 2012-03-12 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Dziś ostatecznie zdenerwowała mnie konieczność wpisywanie hasła podczas logowania przez RDC do komputerów w tej samej domenie.
Problem leży po stronie komputera z którego próbujesz się połączyć. Domyślnie system nie chce automatycznie przekazywać poświadczeń z powodów bezpieczeństwa.
O to rozwiązanie:
| 2012-02-13 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Sharepoint 2010 często lubi wyrzucać błędy które nic człowiekowi nie mówią. Dobrym przykładem jest na przykład ten: “Lista nie istnieje."
Błąd ten pojawiał mi się bardzo tajemniczo zawsze tylko na 1 komputerze w firmie lub czasem w na innych w losowych przeglądarkach jak np.: firefox czy opera.
Okazuje się, że najczęstszym powodem jest błędne ustawienie mapowania dostępu alternatywnego lub brak miejsca w bazie danych.
W moim konkretnym przypadku okazało się, że użytkownik próbował logować się sharepoint wpisując adres z prefiksem “www” co powodowało problem.
Przykład:
Mapowanie było ustawione na http://naszsharepoint.pl użytkownik wpisywał http://www.naszsharepoint.pl, strona działała poprawnie ale siała dziwnymi błędami przy próbie dodania elementu do listy.
Rozwiązaniem było oczywiście poprawienie użytkownika, że adresem jest bez www z przodu i dodaniem w Centrum Administracyjnym SharePoint http://localhost:14694/_admin/AlternateUrlCollections.aspx mapowania także na adres z www. (Link działa tylko na komputerze z zainstalowanym Sharepoint 2010 dodatkowo wasz port może być inny)
PS. Swoją drogą nie rozumiem tego nawyku ludzi do wpisywania www przed nazwami stron. Myślę, że jest do niepotrzebne i mylne.
| 2012-02-11 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Nie dawno natrafiłem na bardzo fajny operator w C#: podwójny znak zapytania “??”. :)
static object DoubleQuestionMarkExample1(object a, object b)
{
return a ?? b;
}
Okazuje się, że można w ten sposób zwrócić A jeżeli A nie jest null lub zwrócić B jeżeli A jest nullem.
Na tym się jednak zabawa nie kończy, możemy zrobić tak:
static object DoubleQuestionMarkExample2(object a)
{
return a ?? new object();
}
Lub nawet:
static object DoubleQuestionMarkExample3(object a)
{
return a ?? (a = new object());
}
Przykład 3 aż się prosi o wykorzystanie przy tworzeniu singletonów:
private static object _a;
public static object A
{
get { return _a ?? (_a = new object()); }
}
Tym sposobem jedną linijką zwrócimy _a jeżeli nie jest nullem, a w przeciwnym wypadku stworzymy nową instancję klasy, przypiszemy ją do _a i zwrócimy jako wartość A.
Dodatkowo fajnym faktem jest możliwość łączenia podwójnych znaków zapytania w łańcuch:
static object DoubleQuestionMarkExample4(object a, object b, object c, object d)
{
return a ?? b ?? c ?? d;
}
Po więcej informacji polecam wątek na stackoverflow http://stackoverflow.com/questions/446835/what-do-two-question-marks-together-mean-in-c
| 2012-02-11 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Wreszcie wziąłem się za posprzątanie mojego bloga.
Poprawiłem kolorowanie kodu, powyłączałem niepotrzebne dodatki, wyczyściłem trochę interface.
Mam nadzieje, że uczyniło to Blog bardziej czytelnym.
| 2012-02-11 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Mieliście kiedyś potrzebę stworzenia generycznej metody która enkapsulowałaby warunek LINQ i która można by użyć w zapytaniu np. do EntityFramework bez utraty IQueryable na IEnumarable?
Ja tak. Na w zapytaniu:
var sessionId = getUserSession();
var sds = DB.UserDetails.Where(d => d.SessionId == sessionId)
.Where(d => d.UserId == competencyScore.EnityId)
.Select(d => d.OfficeUnit).First();
Sprawdzenie czy detale są z danej sesji wymaga dodatkowej zmiennej i warunku, a biorąc pod uwagę, że taki sam warunek przynależności do sesji używam w wielu innych zapytaniach przydało by się go opakować w funkcję, tak aby powstało coś takiego:
var units = DB.UserDetails.InUserSession().Where(d => d.UserId == competencyScore.EnityId) .Select(d => d.OfficeUnit)
Jest to bardziej skomplikowane niż się wydaje ponieważ funkcja powinna być generyczna i najlepiej by rozszerzała IQuerable.
Ostatecznie udało mi się rozwiązać problem używając klasy Expression:
public static IQueryable InUserSession(this IQueryable queryable)
{
var sessionId = getUserSession();
return queryable.AddEqualityCondition("SessionId", sessionId);
}
public static IQueryable AddEqualityCondition(this IQueryable queryable,
string propertyName, V propertyValue)
{
ParameterExpression pe = Expression.Parameter(typeof(T), "p");
IQueryable x = queryable.Where(
Expression.Lambda>(
Expression.Equal(Expression.Property(pe, typeof (T).GetProperty(propertyName)),
Expression.Constant(propertyValue, typeof (V)), false,
typeof (T).GetMethod("op_Equality")), new ParameterExpression[] {pe}));
return (x);
}
Uwaga: AddEqualityCondition nie potrafi porównać typu prostego i jego Nullable więc wracajcie na to uwagę.
| 2011-09-15 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Jeśli otrzymujecie błąd:
fails with Exception information: Exception type: ConfigurationErrorsException Exception message: Unrecognized attribute 'targetFramework'. Note that attribute names are case-sensitive.
To powinieneś zainstalować .NET 4.0 framework oraz uruchomić na serwerze:
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319>aspnet_regiis -i>
Jako ciekawostkę dodam, że ASP.NET 4.0 można wyinstalować komendą:
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319>aspnet_regiis -u
| 2011-09-13 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Natknąłem się ostatnio znów na problem z serwerem Exchange 2010 stojącym na wirtualnym WS2008R2. Przy próbie połączenia z konsolą exchange wyświetlał się komunikat błędu:
Connecting to remote server failed with the following error message : The WinRM client received an HTTP server error status (500), but the remote service did not include any other information about the cause of the failure. For more information, see the about_Remote_Troubleshooting Help topic.
Rozwiązanie okazuje się dość proste, wystarczy wywołać 2 komendy PowerShella:
Import-Module ServerManager Add-WindowsFeature WinRM-IIS-Ext
Które zainstalują WinRM IIS Extensions.
| 2011-07-15 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Inner Exception: An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.
Problem w moim przypadku wystąpił przy próbie połączenia proxy z usługą WCF korzystającej z wsHttpBinding, ponieważ godzina serwera różniła się o około 10min od godziny klienta. Co ciekawe to serwer miał zły czas.
| 2011-07-11 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
netsh routing ip nat add portmapping external tcp 0.0.0.0 5555 192.168.0.2 3389 netsh routing ip nat show interface
| 2011-07-08 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Cześć,
Natknąłem się ostatnio na problem podłączenia się do Exchange 2010 SP1, wykorzystując EWS (Exchange Web Service)
Podczas próby połączenia się do usługi serwer zwracał błąd: “The request failed with HTTP status 401: Unauthorized.” lub cos o niedostępności Active Directory.
Problem dziwny ponieważ dane logowania na pewno podawałem poprawne.
Mój kod wyglądał w ten sposób:
ExchangeService service =
new ExchangeService(ExchangeVersion.Exchange2010_SP1);
service.Credentials = new WebCredentials("temp1", "temp", "domena");
service.TraceEnabled = true;
service.Url = new Uri("http://192.168.0.205/EWS/Exchange.asmx");
ListAppointments(service);
Po przejrzeniu wielu niepomocnych wątków w Internecie spróbowałem przejrzeć się samej klasie ExchangeService gdzie znalazłem:
service.PreAuthenticate = true;
Co natychmiastowo rozwiązało mój problem.
Mam nadzieję, że wam też rozwiązanie pomoże.
| 2011-04-11 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|

Czyszczenie cacha w wypadku problemu z już zinstalowaną aplikacją:
rundll32 dfshim CleanOnlineAppCache
| 2011-03-11 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
Opisze dzisiaj serię problemów które miałem z instalacją Microsoft Exchange 2010 SP1 pod Windows Server 2008 R2 SP1 i migrację danych z Exchange 2003 działającym na Windows Server 2003 R2 SP2 PL który jest również kontrolerem domeny poziomu Windows Server 2003 Native.
OK, oto lista błędów w chronologicznej kolejności:
| 2010-08-15 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
SqlMetal.exe MyDatabase.sdf /dbml:MyDatabase.dbml
| 2010-08-02 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|
W nowym SharePoint 2010 sryptowe zarządzanie zostało przeniesione z STSADM Add-PSSnapin Microsoft.SharePoint.PowerShell
Get-SPFarm
<user_documents>/WindowsPowerShell/Microsoft.PowerShell_profile.ps1
$12HivesDir = "${env:CommonProgramFiles}\Microsoft Shared\web server extensions\14\" [System.Reflection.Assembly]::LoadFrom("$12HivesDir\ISAPI\Microsoft.SharePoint.dll") $devSite = New-Object -TypeName "Microsoft.SharePoint.SPSite" -ArgumentList "http://192.168.0.201/Repozytorium"; $devWeb = $devSite.OpenWeb(); $zadania = $devweb.GetList(“http://192.168.0.201/Repozytorium/Lists/Zadania")</a> $types = @() $dtypes = @() foreach($ctype in $zadania.ContentTypes) { $types += $ctype.name } $opisy = $devweb.GetList(http://192.168.0.201/Repozytorium/Lists/Opisy%20wiadomoci) foreach($ctype in $opisy.items) { $dtypes += $ctype.name } foreach($ctype in $types) { if($dtypes -notcontains $ctype) { Write-Host "Dodaje element: ", $ctype $item = $opisy.AddItem() $item.item("Typ zadania") = $ctype $item.Update() } }
| 2010-08-02 | Posted by Mateusz Świetlicki under Polskie blogi IT |
|

Pracując ostatnio w małej firmie programistycznej natkneliśmy się na pożny problem podczas naszych pierszych kroków z SharePoint 2010.
Po instalacji SharePointFundation wszystko zdawało się działać dobrze dla osoby która go zainstalowała, ale gdy ktoś próbował dostać się z zewnątrz czy nawet później z serwera nagle wszystko działało dziwnie.
Otóż:
Męczyliśmi się z tym kilka dobrych godzin myśląc że to błąd instalacji SP czy specyfikacji serwera. Na koniec jednak okazało się że błąd jest banalny.
Błędy tego typu występują gdy użytkownik próbuje dostać się do serwera podając adres który nie jest skonfigurowany w ustawieniach mapowania alternatywnego.
Jeżeli np. mapowanie jest skonfigurowane na http://mojserwer to dostanie się z http://localhost czy http://192.168.0.1 mimo że teoretycznie poprawne powoduje błędy linków i tym samym w.w. problemy.
Należy więc dodać wszystkie możliwe mapowania do konfiguracji w paneli administracyjnym.