Zwijanie oraz rozwijanie fragmentów strony www

Posted August 28th, 2010 by Piotr Zieliński
Categories: ASP .NET

Często na stronie www chcemy umieścić elementy, które powinny być domyślnie zwinięte aby zajmowały mniej miejsca. Na przykład na tej stronie zdecydowałem, że filtrowanie wyników powinno domyślnie być schowane aby zajmowało mniej miejsca. Klikając na link “Pokaż\Ukryj” użytkownik może rozwinąć okno. Warto również zwrócić uwagę na animacje – okno nie pojawia się od razu ale stopniowo jest rozszerzane.

Zaprezentowany wynik można łatwo uzyskać za pomocą biblioteki jQuery. Po ściągnięciu biblioteki oraz podłączeniu stosownych plików JS należy napisać następujący skrypt:

<script type="text/javascript">
    $(document).ready(function () {
        $("#msg_body").hide();
    });
</script>

Metoda hide (ukrycie formularza) zostanie wywoła w momencie zainicjowania biblioteki jQuery. Selektor #msg_body oznacza wybranie elementu w którym atrybut id jest równy msg_body. Następnie można przejść do zdefiniowania reszty formularza:

<%using(Html.BeginForm("Search","Politicians",FormMethod.Get)){ %>

<div id="msg_head">

    <a onclick='javascript:$("#msg_body").slideToggle(600);return false' href='#' class="marked_link">
        <img  src="/images/search.png" alt="Filtrowanie" style="float:left" />Filtrowanie (Pokaż\Ukryj)
    </a>

</div>

<br />
<div id="msg_body">
    <fieldset>

        <legend>Filtrowanie</legend>
        <p>Imię:
            <br />
            <%=Html.TextBox("FirstName", Request.QueryString["FirstName"]) %>
        </p>

        <p>Drugie imię:
            <br />
            <%=Html.TextBox("SecondName", Request.QueryString["SecondName"]) %>
        </p>

        <p>Nazwisko:
            <br />
            <%=Html.TextBox("LastName", Request.QueryString["LastName"]) %>
        </p>
        <input type=submit value="Filtruj" />
    </fieldset>
</div>
<%} %>

Element div z id równym msg_body zawiera dane, które będą zwijanie oraz rozwijane. Z kolei w msg_head został umieszczony link odpowiedzialny za wykonanie operacji. W zdarzeniu OnClick umieszczono następujący kod JS:

'javascript:$("#msg_body").slideToggle(600);return false'

Kluczem do sukcesu jest metoda slideToggle, która odpowiada za animację zwijania lub rozwijania. Jako parametr wejściowy można przekazać całkowity czas animacji.

Funkcja string.Trim a T-SQL

Posted August 23rd, 2010 by Piotr Zieliński
Categories: SQL Server

W .NET mamy do dyspozycji funkcję Trim (string.Trim) służącą do usuwania pustych znaków (spacji) zarówno przed jak i po stringu. Przykład:

string text="    jakiś tekst  ";
trimmedText=text.Trim();
// teraz trimmedText równy jest "jakiś tekst"

Jak widać spację zostały usunięte. W T-SQL nie mamy dokładnie takiej samej funkcji ale łatwo uzyskać identyczny efekt za pomocą LTRIM oraz RTRIM:

LTRIM(RTRIM(kolumna))

Najpierw usuwamy wszystkie znaki z prawej strony a potem dopiero z lewej. Można również napisać własną funkcję opakowującą powyższy kod.

ASP.NET MVC – obrazek jako link do asynchronicznego wywołania

Posted August 19th, 2010 by Piotr Zieliński
Categories: ASP .NET, Patterns & Practices

W poprzednich postach pokazałem jak tworzyć asynchroniczne formularze. Nie zawsze jednak chcemy wywoływać takie zapytania za pomocą przycisku Submit. Czasami lepiej użyć linku tekstowego lub po prostu obrazka. Ajaxowy link tekstowy bardzo łatwo utworzyć za pomocą metody ActionLink:

<%=Ajax.ActionLink("Usuń","RemoveAssocation",new AjaxOptions(){}) %>

Pytanie jednak co zrobić aby obrazek posłużył jako ajaxowy link? W poście przedstawię może nieeleganckie ale za to bardzo proste rozwiązanie:

<%= Ajax.ActionLink("[replacethis]", ...).Replace("[replacethis]", "<img src=\"/images/remove.gif\" ... />" %>

Najpierw generujemy zwykły, tekstowy link (<a href=’…’>[replacethis]</a>), a następnie wystarczy zamiast tekstu wstawić obrazek (metoda Replace). Dla porządku w kodzie można napisać rozszerzenie do AjaxHelpera:

static public class AjaxHelperEx
{
   public static string ImageActionLink(this AjaxHelper helper, string imageUrl, string altText, string actionName, object routeValues, AjaxOptions ajaxOptions)
   {
       var builder = new TagBuilder("img");
       builder.MergeAttribute("src", imageUrl);
       builder.MergeAttribute("alt", altText);
       string link = helper.ActionLink("[replaceme]", actionName, routeValues, ajaxOptions).ToHtmlString();
       return link.Replace("[replaceme]", builder.ToString(TagRenderMode.SelfClosing));
   }
}

Teraz w widoku wystarczy, że wywołamy:

<%=Ajax.ImageActionLink("/images/remove.png","Usuń","RemoveAssocation",null,new AjaxOptions(){}) %>

ASP.NET MVC – asynchroniczne wysyłanie formularzy (AJAX) na przykładzie prostego ShoutBox’a

Posted August 12th, 2010 by Piotr Zieliński
Categories: ASP .NET, Patterns & Practices

Podobnie jak w czystym ASP.NET, framework ASP.NET MVC pozwala na wysyłanie formularzy w tle. Załóżmy, że chcemy napisać shoutbox’a, w którym wysłanie wiadomości na serwer nie wymagałoby ponownego przeładowania całej strony. Zamiast definiowania formularza za pomocą Html.BeginForm, musimy skorzystać z Ajax.BeginForm:

<% using(Ajax.BeginForm("AddMessage",new AjaxOptions(){ OnComplete="OnFinish", UpdateTargetId="messages"}))
{ %>
// treść
<%}%>

Pierwszy parametr to nazwa akcji. W drugim parametrze przekazujemy klasę AjaxOptions określającą między innymi zdarzenie w JavaScript wywoływane po zakończeniu zapytania oraz identyfikator elementu modyfikowanego w trakcie wykonywania akcji. Stwórzmy więc prosty ShoutBox:

<div  id="messages" style="width: 350px; height: 80px; overflow:auto;">
<%Html.RenderPartial("Messages"); %>
</div>
<%
   using(Ajax.BeginForm("AddMessage",new AjaxOptions(){ OnComplete="OnFinish", UpdateTargetId="messages"}))
   { %>

   <%if(Page.User.Identity.IsAuthenticated==false){ %>
   Nick:
   <br />
   <%=Html.TextBox("Nick", "", new { maxlength = 50, style = "width:220px" })%>

   <%} %>
   <br />
   Wiadomość:
   <br />
   <%=Html.TextBox("Message", "Wpisz opinię, która pojawi się na mównicy...", new { maxlength = 500, title = "Wpisz opinię, która pojawi się na mównicy...", @class = "water", style = "width:220px" })%>

   <input type="submit"  value="Wyślij" />

<%} %>

Najpierw definiujemy element div z ID ustawionym na “messages”. Następnie tworzymy asynchroniczny formularz za pomocą Ajax.BeginForm. Zawartość formularza nie zawiera już żadnej nowości – jedynie standardowe pola: jedno na nick a drugie na wiadomość. Przyjrzyjmy się jeszcze akcji AddMessage:

public ActionResult AddMessage(string message)
{
  Repositories.Shoutbox.ShoutboxRepository repository = new Repositories.Shoutbox.ShoutboxRepository();
  if(string.IsNullOrEmpty(message))
      return PartialView("Messages", repository.GetNewestMessages());

  Entities.ShoutBoxMessage shoutMessage = new Entities.ShoutBoxMessage() { Message = message };
  shoutMessage.ID_MESSAGE = Guid.NewGuid();
  shoutMessage.Date = DateTime.Now;

  if (User.Identity.IsAuthenticated == false)
  {
      if (!string.IsNullOrEmpty(Request["Nick"]))
      {
          shoutMessage.UserName = Request["Nick"];
      }
  }
  else
      shoutMessage.ID_USER = new Guid(System.Web.Security.Membership.GetUser().ProviderUserKey.ToString());

  repository.Create(shoutMessage);
  repository.SubmitChanges();
  return PartialView("Messages", repository.GetNewestMessages());
}

Kod tworzy wiadomość (klasa ShoutBoxMessage) oraz dodaje ją do baz danych. Następnie akcja zwraca widok “Messages” z zawartością wszystkich wiadomości (repository.GetNewestMessages). Widok częściowy (PartialView) jest dodawany do elementu div (messages) w sposób asynchroniczny. Nie wymaga to oczywiście przeładowania strony. Na zakończenie jeszcze widok Messages.ascx (dodawany do wspomnianego już div):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<PoliticiansPromises.Entities.ShoutBoxMessage>>" %>
<%@ Import Namespace="PoliticiansPromises.Extensions" %>
<% foreach(var item in Model){ %>

<%if(item.ID_USER==null){ 

      if(item.UserName==null){
      %>
      <span class="marked_link">~Anonim</span>
      <%}else{ %>
      <span class="marked_link">~<%:item.UserName %></span>
      <%} %>
<%}else{ %>

<span class="marked_link"><%:System.Web.Security.Membership.GetUser(item.ID_USER).UserName %></span>

<%} %>

<span class="small_gray_style"><%: String.Format("{0:g}", item.Date)%></span>
<%=Html.EncodeAndCut(item.Message,500) %>
<br />

<%} %>
<br />

T-SQL i NULLIF

Posted August 8th, 2010 by Piotr Zieliński
Categories: SQL Server

Dzisiaj bardzo króciutki post. Do tabeli dodałem computed column (wartość liczona na podstawie innych kolumn). Formuła obliczająca wartość wyglądała następująco:

VotesFor / TotalVoteCount

Chciałem po prostu dodać kolumnę, która liczy procent oddanych głosów aby potem móc stworzyć indeks po tej kolumnie. W przypadku jednak gdy TotalVoteCount wynosi 0 pojawił się wyjątek Divide by Zero. Na szczęście funkcja NULLIF rozwiązała problem:

VotesFor / NULLIF(TotalVoteCount,0)

W sytuacji gdy TotalVoteCount równy jest 0, NULLIF zwróci wartość NULL i tym samym całość formuły zakończy się poprawnie z wynikiem również NULL. Proste rozwiązanie ale mam nadzieje, że komuś się przyda :)

ASP.NET MVC oraz sortowanie wyników po kolumnach

Posted August 1st, 2010 by Piotr Zieliński
Categories: ASP .NET, Patterns & Practices

W jednym z poprzednich postów pokazałem jak zrealizować stronicowanie wyników. Kontrolka ListView z czystego ASP.NET miała jeszcze jedną zaletę – możliwość sortowania wyników po kolumnach. W dzisiejszym poście zaprezentuję sposób uzyskania takiego efektu w ASP.NET MVC w którym jak wiemy nie ma żadnych wbudowanych kontrolek.

image 

Dla zwizualizowania, efekt końcowy pokazuję na powyższym screenie. Mamy kilka kolumn, które są linkami. Po kliknięciu na daną kolumnę chcemy aby wyniki były posortowane za pomocą tej kolumny. Po drugim kliknięciu w tą samą kolumnę kierunek sortowania zostaje odwrócony (z DESC na ASC i z ASC na DESC).

Zdecydowałem się na napisanie metody rozszerzającej HtmlHelper:

public static class HtmlHelper
{
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues)
   {
       return SortableColumn(htmlHelper, linkText, columnName, routeValues, "sord", "sidx",null);
   }
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues, string sortTypeParName, string sortColumnParName)
   {
       return SortableColumn(htmlHelper, linkText, columnName, routeValues, sortTypeParName, sortColumnParName);
   }
   public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues,string sortTypeParName,string sortColumnParName,string anchorName)
   {
       System.Web.Routing.RouteData data = htmlHelper.ViewContext.Controller.ControllerContext.RouteData;
       string actionName = data.GetRequiredString("action");

       StringBuilder sb = new StringBuilder();
       var vals = new RouteValueDictionary(routeValues);
       string sidx = String.Empty;
       if (System.Web.HttpContext.Current.Request[sortColumnParName] != null)
       {
           sidx = System.Web.HttpContext.Current.Request[sortColumnParName].ToString();
       }
       //modify the sidx       
       if (vals.ContainsKey(sortColumnParName) == false)
       {
           vals.Add(sortColumnParName, columnName);
       }
       else
       {
           vals[sortColumnParName] = columnName;
       }

       string sord = String.Empty;
       if (System.Web.HttpContext.Current.Request[sortTypeParName] != null)
       {
           sord = System.Web.HttpContext.Current.Request[sortTypeParName].ToString();
       }
       //add the sord key if needed      
       if (vals.ContainsKey(sortTypeParName) == false)
       {
           vals.Add(sortTypeParName, String.Empty);
       }
       //if column matches    
       if (sidx.Equals(columnName, StringComparison.CurrentCultureIgnoreCase) == true)
       {
           if (sord.Equals("asc", StringComparison.CurrentCultureIgnoreCase) == true)
           {
               //draw the ascending sort indicator using the wingdings font.  
               sb.Append(" <img src='/images/asc.png' alt='asc'/>");
               vals[sortTypeParName] = "desc";
           }
           else
           {
               sb.Append(" <img src='/images/desc.png' alt='desc'/>");
               vals[sortTypeParName] = "asc";
           }
       }
       else
       {
           vals[sortTypeParName] = "asc";
       }
       if(anchorName==null)
           sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName,vals));
       else
           sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName, null, null, null, anchorName, vals, null));

       return sb.ToString();
   }
}

Powyższy kod jest przeróbką przykładu znalezionego na jednym z serwisów (nie pamiętam już niestety nazwy). Metoda przyjmuje kilka parametrów:

  1. object routeValues – tak samo jak w przypadku ActionLink (dodatkowe parametry do przekazania w URL).
  2. string sortTypeParName – nazwa parametru QueryString, który będzie przechowywać typ sortowania.
  3. string sortColumnParName – nazwa parametru QueryString, który będzie przechowywać nazwę kolumny po której należy sortować.
  4. string anchorName – dodatkowy parametr przechowujący nazwę kotwicy (np. #comments). Jeśli przekażemy null metoda zignoruje po prostu kotwicę.

Sposób wykorzystania metody jest zdecydowanie łatwiejszy. W widoku wystarczy:

<table class="tableList">
    <tr>

        <th><%=Html.SortableColumn("Obietnica", "ModificationDate", null, "promisesSortType", "promisSortColumnName", "promises")%></th>
        <th><%=Html.SortableColumn("Źródło", "Source", null, "promisesSortType", "promisSortColumnName", "promises")%></th>
    </tr>
    ...
</table>

W powyższym przykładzie ostatni parametr (promises) odpowiada za dołączenie kotwicy #promises. SortableColumn wygeneruje odpowiedni link z tekstem i obrazkiem symbolizującym kierunek sortowania (zielona strzałka na pierwszym screenie).

Pozostało jeszcze pokazać zawartość kontrolera, który odpowiada za faktyczne posortowanie danych:

public ActionResult Details(Guid id)
{
  string promisesOrderString = (Request.QueryString["promisesSortColumnName"] ?? "ModificationDate") + " " + (Request.QueryString["promisesSortType"] ?? "asc");

  PromisesRepository promisesRepository = new PromisesRepository();
  IQueryable<Promises> allOrderedPromises= promisesRepository.GetAllOrderedBy()(promisesOrderString);

  ...
  return View(viewModel);
}

Kluczem jest wygenerowania string’a w formacie ‘COLUMN_NAME ASC’ lub ‘COLUMN_NAME DESC’. W powyższym kodzie promisesOrderString zawiera właśnie taki  string. Następnie używamy jednej z przeładowanych metod OrderBy i przekazujemy promisesOrderString. Należy zaznaczyć, że domyślnie LINQ nie zawiera metody OrderBy akceptującej jako parametr wejściowy zmienną typu string. Należy więc ją dodać jako rozszerzenie (kod klasy z CodePlex).

Integration Services i błąd DTS_E_CANNOTACQUIRECONNECTIONFROMCONNECTIONMANAGER

Posted July 29th, 2010 by Piotr Zieliński
Categories: SQL Server

Dziś pisząc pewien pakiet integracyjny (integration services) napotkałem na następujący błąd:

Error: SSIS Error Code DTS_E_CANNOTACQUIRECONNECTIONFROMCONNECTIONMANAGER.  The AcquireConnection method call to the connection manager "Excel Connection Manager 1" failed with error code 0xC00F9304.  There may be error messages posted before this with more information on why the AcquireConnection method call failed.

Stworzony pakiet składał się z Excel Source. Po odpaleniu wystąpił błąd właśnie na tym elemencie. Przyczyną okazało się brak wsparcia dla procesorów 64-bitowych. Rozwiązaniem jest ustawienie właściwości Run64BitRuntime na false. Z menu głównego klikamy na Project->Properties i w otwartym oknie ustawiamy właściwość na false:

image

ASP.NET MVC i stronicowanie wyników

Posted July 27th, 2010 by Piotr Zieliński
Categories: ASP .NET, Patterns & Practices

W ASP.NET MVC nie ma wbudowanego wsparcia stronicowania dla wyświetlanych danych. W czystym ASP .NET mieliśmy do dyspozycji wiele zaawansowanych kontrolek, które wspierały mechanizm stronicowania (np. ListView). W ASP.NET MVC interfejs definiujemy za pomocą czystego HTML\XHTML i sami musimy zadbać o możliwość stronicowania. Nie jest to trudne ale wymaga jednak trochę nakładu pracy. Jeśli chcemy mieć elastyczną bibliotekę do obsługi stronicowania to implementacja jest już dość czasochłonna. Dlatego w tym poście przedstawię gotową bibliotekę MvcPager załatwiająca większość rzeczy za nas. Niezbędne pliki możecie pobrać ze strony projektu tutaj. 

Po ściągnięciu plików należy najpierw dodać bibliotekę MVCPager do referencji. Następnie w widoku, który chcemy użyć stronicowania niezbędne jest zaimportowanie biblioteki za pomocą dyrektywy import:

<%@ Import Namespace="Webdiyer.WebControls.Mvc"%>

Następnie można wygenerować elementy graficzne odpowiedzialne za stronicowanie:

<%=Html.Pager(Model, new PagerOptions
{

    CurrentPagerItemWrapperFormatString = "<span class=\"cpb\">{0}</span>",
    NumericPagerItemWrapperFormatString = "<span class=\"item\">{0}</span>",
    CssClass = "pages",
    PageIndexParameterName = "quotePageIndex",
    NextPageText = ">>>",
    PrevPageText = "<<<",
    ShowDisabledPagerItems = false,
    FirstPageText = "Pierwszy",
    SeparatorHtml = "" ,
    NumericPagerItemCount=5,
    LastPageText = "Ostatni"
}, "quotes")
    %>

Jak widać biblioteka umożliwia bardzo dokładną konfiguracje. Programista może m.in. określić wykorzystywany styl css lub ilość wyników na pojedynczej stronie. W funkcji Pager jako pierwszy parametr przekazuje się kolekcję danych a konkretnie Webdiyer.WebControls.Mvc.PagedList. Aby stronicowanie działało koniecznie musimy wykorzystać ten konkrety typ ale istnieje konwerter, który umożliwia łatwą zamianę np. IQueryable na PagedList:

Webdiyer.WebControls.Mvc.PagedList<Entities.Quote> quotes=null;
// quotes to IQueryable
pagedQuotes = quotes.ToPagedList(colDefs.QuotePageIndex, colDefs.PageSize);

image

W wyniku powyższego kodu dostaniemy stronicowanie pokazane na screenie. Implementacja samodzielna wcale nie jest taka prosta – głównie ze względu na wyświetlanie numerów stron. Poza tym poco męczyć się z czymś co jest już od dawna rozwiązane i dostępne publicznie za darmo?

WCF Data Services – artykuł

Posted July 17th, 2010 by Piotr Zieliński
Categories: EntityFramework, WCF

Jakiś czas temu (kilka miesięcy;)) obiecywałem, ze napiszę artykuł o WCF Data Service. Zainteresowanych odsyłam tutaj.

ASP.NET MVC, Html.Editor(…), Html.EditorFor(…)

Posted July 15th, 2010 by Piotr Zieliński
Categories: ASP .NET, Patterns & Practices

W poprzednim poście przedstawiłem metodę Html.Display. Funkcja Html.Editor(…) w działaniu jest bardzo podobna – również służy do generowania szablonów na podstawie klas. Różnica polega na tym, że Html.Editor tworzy pola edycyjne TextBox. Służy więc do generowania formularzy edycyjnych a nie do prezentowania danych tak jak Html.Display. Sposób wykorzystania jest analogiczny do DisplayFor więc zachęcam do przeczytania poprzedniego wpisu. Poniżej końcowy wynik:

image

Warto wspomnieć, że również można tworzyć własne szablony. Umieszczać należy je w formie widoków częściowych w folderze EditorTemplates (zamiast DisplayTemplates).