Grupy w wyrażeniach regularnych

Grupy w wyrażeniach regularnych są dla mnie czymś super! Umożliwiają one wyselekcjonowanie konkretnych sekcji danych z tekstu na którym operujemy wyrażeniem.

Oto przykład:

        String sample = "hello-world-";
        Regex regex = new Regex("-(?<test>[^-]*)-");

        Match match = regex.Match(sample);

        if (match.Success)
        {
            Console.WriteLine(match.Groups["test"].Value); //hello
        }

Wykorzystanie grup w wyrażeniu regularnym polega na obłożeniu fragmentu, który chcemy wyciągnąć z wyniku znalezionego przez wyrażenie kodem:
(?<nazwaGrupy>fragmentDoWyciągnięcia)

Oczywiście możemy wykorzystać kilka grup w jednym wyrażeniu.

Walidacja zdalna w ASP.NET MVC za pomocą atrybutu Remote

W asp.net mvc pojawił się atrybut Remote, który nakładamy na pole z modelu zbindowanego do widoku.

public class User
{
[Remote("CheckUserExist","Register",ErrorMessage = "User exist.")]
public string UserName {get;set;}

public string Password {get;set;}
}

metoda i kontroler jak z wcześniejszego postu:

public class RegisterController : Controller     
{   
        [HttpPost]
        public ActionResult CheckUserExist(string userName)
        {
           bool exist = false;
           //inna logika walidująca
            return Json(exist);
        }
}

Dodatkowo można dla atrybutu ustawić ErrorMessage, który pojawi się na UI w razie nieprawidłowej walidacji oraz AdditionalFields który służy do dodania kolejnych pól które chcemy wziąć pod uwagę przy walidacji i znajdują się one w modelu w którym nadaliśmy atrybut. Kolejne pola dodajemy po przecinku.

Do web.config należy dodać:

<appSettings>
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>

 
Po wczytaniu strony asp.net można zauważyc, że zostały wygenerowane customowe atrybuty data-val-remote, zawierające informacje o naszej walidacji.

Wywoływanie metod kontrolera z javascriptu

Aby móc wywoływać metody controllera z poziomu javascriptu, a konkretnie jQuery, należy dodać referencje do biblioteki jQuery, choć najczęściej jest juz ona dodawana do projektów webowych w visual studio.

Następnie należy dodać do kontrolera metodę:

 public class RegisterController : Controller     
{   
        [HttpPost]
        public ActionResult CheckUserExist(string userName)
        {
           bool exist = false;
           //inna logika walidująca
            return Json(exist);
        }
}

a do javascriptu:

function CheckIfUserExist() {
var isUserExist = false;
        $.ajax({
        type: "POST",
        datatype: 'json',
        url: '/Register/CheckUserExist',
        data: JSON.stringify({ "userName": UserName }),
        contentType: 'application/json; charset=utf-8',
        success: function (data) {
            if (data == false) {
                alert("Podany user już istnieje");
                isUserExist= false;
            }
            else {
                isUserExist= true;
            }
        },
        complete: function () {
        },
        async: false
    });
    return isUserExist;
}

<input type="button" value="Rejestruj" onclick="CheckIfUserExist();" />

Nadzą funkcje z javascriptu podpinamy pod przycisk na formie. Gdy następuje submit, zostaje wysłany request do controllera Register do metody CheckUserExist. Metoda ta zwraca wartość boolowską, którą łapiemy w javascripcie i na tej podstawie walidujemy kliknięcie przycisku.
Pamiętajmy, że aby ten przykład działał należy dodać zmodyfikować routing, aby nasz controller Register był defaultowy.

routes.MapRoute( 
"Default", // Route name 
"{controller}/{action}/{id}", // URL with parameters
 new { controller = "Register", action = "Index", id = UrlParameter.Optional } // Parameter defaults 
);

Jeśli tak nie jest należy zmodyfikować url w jQquery aby wskazywał na controller

Asp.net Ajax Toolkit – Extender controls

Długi czas miałem problem z używanie kontrolek z pakietu Ajax Toolkit, których nazwy kończyły się na Extender. Było dla mnie niewadome jak nalezy ich użyć, dlatego teraz przedstawię ich działanie na kilku przykładach ;)

Kontrolki opisane jako Extender są rozszerzeniami do już istniejących kontrolek Asp.net. Nie każdą kontrolkę standardową da się rozszerzyć każdym rozszerzeniem, każda ma przypisane konkretne rozszerzenia.
Pamiętajmy o zarejestrowaniu dllki zawierającej kontrolki:

<%@ Register TagPrefix="asp" Namespace="AjaxControlToolkit" Assembly="AjaxControlToolkit" %>

 

  •  CalendarExtender

Weźmy jako pierwszy przykład kontrolkę TextBox. Możemy ją rozszerzyć za pomocą CalendarExtender‘a. Powoduje to, że klikając w pole do wpisania ukazuje się nam kalendarz. Jest to dla mnie dużo lepiej wyglądające rozwiązanie niż zwykły kalendarz, który zajmuje sporo miejsca na stronie. Po wybraniu daty zostaje ona przypisana do tekstu TextBox’a.

 <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
            <asp:CalendarExtender ID="TextBox1_CalendarExtender" runat="server" Enabled="True"
                TargetControlID="TextBox1">
            </asp:CalendarExtender>

W kodzie zauważamy, że w samym TextBox’ie nic się nie zmienia, tylko kontrolka rozszerzająca przechowuje ID kontrolki, którą rozszerza w polu TargetControlID. Bardzo wygodne rozwiązanie.

  • ToggleButtonExtender


Kontrolka ta pozwala na checkbox’a nałożyć ikony, które będą reprezentowały jego wygląd, gdy jest on wciśnięty lub nie

asp:CheckBox ID="CheckBox1" runat="server" OnCheckedChanged="CheckBox1_CheckedChanged"
                    AutoPostBack="true" />
                <asp:ToggleButtonExtender ID="CheckBox1_ToggleButtonExtender" runat="server" CheckedImageUrl="~/Styles/lep-icon-bulb.png"
                    ImageHeight="20" ImageWidth="20" UncheckedImageUrl="~/Styles/images.jpg" Enabled="True"
                    TargetControlID="CheckBox1">
                </asp:ToggleButtonExtender>
                <asp:Label ID="Label1" runat="server" Text="Some error was solved."></asp:Label>

Do CheckedImageUrl oraz UncheckedImageUrl przypisujemy ścieżki do obrazków wyświetlanych w odpowiednim stanie kontroli checkbox. Dodatkowo ja dodałem kontrolkę Label, której tekst pojawia się jako komunikat, gdy checkbox jest zaznaczony. Daje to ciekawy efekt, u mnie pojawiają się akurat tam podpowiedzi dla usera, które może schować gdy już przeczyta ;) Aby sztuczka z Labelem zadziałała należy event CheckedChange dla Checkboxa dodać do Triggerów UpdatePanela.

  • SliderExtender
<asp:SliderExtender ID="SliderExtender1" BoundControlID="slider1_display" Decimals="0"
                Minimum="0" Maximum="255" runat="server" TargetControlID="TextBox2" EnableHandleAnimation="true"
                TooltipText="{0}">
            </asp:SliderExtender>
            <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
            <asp:Label ID="slider1_display" runat="server"></asp:Label>

Dzieki temu rozszerzeniu na TextBox’ie pojawia się slider za pomoca którego możemy wybierać wartość liczbową z określonego przedziału za pomocą MinimumMaximum. Dobrze jest dodać kontrolkę Label aby aktualna wybrana wartość wyświetliła się właśnie w niej. Dla mnie to rozwiązanie wydało się pomocne dla wybierania przez usera czasu. Ponieważ nie ma kontrolki pozwalającej wybrać tylko czas, dodałem 3 wersje tego rozszerzenia dla godziny, minuty i sekundy. Jest to wg mnie lepsze rozwiązanie niż np samotne TexBox’y gdyż user nie może wpisać niepoprawnej wartości ;)

Parallel.For testowanie

Hej,

wzięło mi się na przetestowanie rzeczywistej wydajności owej funkcji. Jeszcze produkcyjnie nie używałem jej więc postanowiłem użyć jej praktycznie w teście.

Nasz kod testujący wygląda następująco:

class Program
	{
		static void Main(string[] args)
		{
			Stopwatch sw=new Stopwatch();
			int start=0;
			int end=1000000000;
			sw.Start();
			ForTests.ForTest(start,end);
			sw.Stop();
			Console.WriteLine(string.Format("For time {0}",sw.ElapsedTicks));

			sw.Reset();
			sw.Start();
			ForTests.ForParallelTest(start,end);
			sw.Stop();
			Console.WriteLine(string.Format("For Parallei time {0}",sw.ElapsedTicks));
			Console.Read();
		}
	}

Mamy prostą metodę wykorzystująca for nie robiąca nic:

		public static void ForTest(int start,int end)
		{
			for(int i=start;i<end;i++)
			{
			}
		}

Taka sama metoda w wersji parallel:

		public static void ForParallelTest(int start,int end)
		{
			Parallel.For(start,end,(item) =>
			{
			});
		}

Po przetestowaniu na 1000000000 elementów dostajemy takie wyniki w taktach zegara:

For -  6531089
Parallel - 10511590

Wielowątkowość przegrywa :(

Teraz nieco zmodyfikujmy nasze metody dodając taką linijkę:
dla for -> double x=Math.Pow(i,i);
dla parallel -> double x=Math.Pow(item,item);

Po zmianach wyniki są całkiem ciekawe:
For – 335149431
Parallel – 119642451
Wielowątkowa wersja jest lepsza 3 krotne. Wychodzi na to, że wersja wielowątkowa nadaję się do zadań pamięciożernych, a dlaczego to już wyjaśni tekst znaleziony na stackoverflow ;)

Suppose you have a task to perform. Let’s say you’re a math teacher and you have twenty papers to grade. It takes you two minutes to grade a paper, so it’s going to take you about forty minutes.

Now let’s suppose that you decide to hire some assistants to help you grade papers. It takes you an hour to locate four assistants. You each take four papers and you are all done in eight minutes. You’ve traded 40 minutes of work for 68 total minutes of work including the extra hour to find the assistants, so this isn’t a savings. The overhead of finding the assistants is larger than the cost of doing the work yourself.

Now suppose you have twenty thousand papers to grade, so it is going to take you about 40000 minutes. Now if you spend an hour finding assistants, that’s a win. You each take 4000 papers and are done in a total of 8060 minutes instead of 40000 minutes, a savings of almost a factor of 5. The overhead of finding the assistants is basically irrelevant.

Parallelization is not free. The cost of splitting up work amongst different threads needs to be tiny compared to the amount of work done per thread.



							

UpdateProgress AJAX extension

Gdy używa się wywołań asynchronicznych problemem dla mnie było po jakimś czasie, że user po akcji czekał na jej wykonanie, ale nie mógł oszacować czy już się zakończyła. Aby to umożliwić można użyć kontrolki UpdateProgress.

Dodajemy ją równolegle do kontrolki UpdatePanel:

<asp:UpdateProgress ID="UpdateProgress1" runat="server">
        <ProgressTemplate>
            <div id="loaderBackground" style="position: absolute; visibility: visible; border: none;
                z-index: 100; width: 60%; height: 50%; background: #999; filter: alpha(opacity=80);
                -moz-opacity: .8; opacity: .8;">
            <div style="position: absolute; right: 50%; top: 50%; z-index: 200;">
                    <img src="../../../Styles/images/ajax-loader.gif" />
                </div>
            </div>
        </ProgressTemplate>
    </asp:UpdateProgress>

Zawartość sekcji ProgressTemplate to content który ukarzę się użytkownikowi podczas wywołania asynchronicznego. W moim przykładzie będzie to szare tło na którym będzie mała animacja.
Aby ulepszyć tą wersje ‘zasłaniacza’ strony w postaci szarego tła które posiada określoną szerokość i wysokość i nie dostosowuje się do zmian na stronie dodałem taki kod javascript:

<script type="text/javascript">
        var manager = Sys.WebForms.PageRequestManager.getInstance(); manager.add_beginRequest(beginRequest);

        function beginRequest() {
            var height = document.getElementById('ContentPlaceHolder1_UpdatePanel1').clientHeight;
            var width = document.getElementById('ContentPlaceHolder1_UpdatePanel1').clientWidth;
            document.getElementById('loaderBackground').style.height = height + 'px';
            document.getElementById('loaderBackground').style.width = width + 'px';
        } 
    </script>

Pobieramy instancje RequestMenagera i dodajemy funkcje która wywoła się przed postbackiem.
Kod pobiera wysokość i szerokość dla UpdatePanelu i ustawia ją dla naszego tła zasłaniającego.

Quartz.Net czyli chciałbym abyś zrobił to kiedy ja chcę

Ostatnio w moim projekcie pojawił się problem wywoływania konkretnych zadań w określonym czasie wybieranym przez użytkownika. Pierwsze co przyszło mi do głowy to MSMQ, jednak aplikacja asp.net, znajduje sięna hostingu i nie będę mógł tam postawic sobie tego narzędzia :/

Szukałem dalej i w odpowiedziach zaczeła przwijac się nazwa nieznanej mi do tej pory biblioteki - Quartz.Net. To kolejna biblioteka która pojawiła się w środowisku .netowym od Javy.

Oto co potrafi:

  • Działanie jako serwis windowsowy lub w samej aplikacji
  • Planowanie zadań
  • Wykonywanie zadań
  • Trwałość zadań (przechowywanie ich w RAM-ie lub bazie danych)
  • Clustering
  • Listeners & Plug-ins

Implementacja biblioteki zawiera się w paru krokach:

  • Dodajemy biblioteke ściągniętą z quartznet.sourceforge.net
  • Dodajemy konfiguracje pliku we.config
  • Implementujemy własny Job
  • Dodajemy do Global.asax aby Scheduler zaczął pracę

Aby dodać własnego Job-a należy dziedziczyć z klasy Job

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Quartz;
public class Job : IJob
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

#region IJob Members
public void Execute(IJobExecutionContext context)
{
// This job simply prints out its job name and the
// date and time that it is running
JobKey jobKey = context.JobDetail.Key;
log.InfoFormat(“SimpleJob says: {0} executing at {1}”, jobKey, DateTime.UtcNow.ToString());
}
#endregion
}

W metodzie Execute dodajemy to co chcielibyśmy aby się wykonało. Parametr context pozwala nam podczas wykonywania zadania sprawdzić jego parametry jak np key. Ja w przykładzie wykorzystuje bibliotekę log4net aby zalogować są informacje, że zadanie zostało wykonane i o jakiej porze.

Zarządzaniem zadaniami zajmuje się Scheduler. Dobrze jest stworzyć klasę obsługująca go jako singleton:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Quartz.Impl;
using Quartz;
using System.Collections.Specialized;
public class Scheduler
{
static Scheduler()
{
NameValueCollection properties = new NameValueCollection();
properties["quartz.scheduler.instanceName"] = “myApp”;
properties["quartz.scheduler.instanceId"] = “MyApp”;
properties["quartz.threadPool.type"] = “Quartz.Simpl.SimpleThreadPool, Quartz”;
properties["quartz.threadPool.threadCount"] = “10″;
properties["quartz.threadPool.threadPriority"] = “Normal”;
properties["quartz.scheduler.instanceName"] = “TestScheduler”;
properties["quartz.scheduler.instanceId"] = “instance_one”;
properties["quartz.jobStore.type"] = “Quartz.Impl.AdoJobStore.JobStoreTX, Quartz”;
properties["quartz.jobStore.useProperties"] = “true”;
properties["quartz.jobStore.dataSource"] = “default”;
properties["quartz.jobStore.tablePrefix"] = “QRTZ_”;
// if running MS SQL Server we need this
properties["quartz.jobStore.lockHandler.type"] = “Quartz.Impl.AdoJobStore.UpdateLockRowSemaphore, Quartz”;
properties["quartz.dataSource.default.connectionString"] = “Server=localhost;Database=db;Uid=lukas;Pwd=Pass”;
properties["quartz.dataSource.default.provider"] = “SqlServer-20″;

_schedulerFactory = new StdSchedulerFactory(properties);
_scheduler = _schedulerFactory.GetScheduler();
}
public static IScheduler GetScheduler()
{
return _scheduler;
}

private static readonly ISchedulerFactory _schedulerFactory;
private static readonly IScheduler _scheduler;
}

W kolekcji properties znajduje się konfiguracja dla biblioteki. Może się ona również znaleźć w pliku web.config, jednak zauważyłem, że podawanie jej bezpośrednio w konstruktorze SchedulerFactory sprawdza się lepiej.
W moim przykładzie wykorzystuje baze danych jako przechowalnie dla moich zadań. Można również uzyć pamęci do tego i użyć jako wartość dla jobStore.type “ ”Quartz.Impl.RAMJobStore, Quartz”;”. Dla mnie baza danych jest pewniejsza, gdyż dane będą tam bezpieczne nawet po restacie pool-i aplikacji. Dodatkowe wartości to prefix tabel i connection sting bazy. Co należy wspomnieć to, że skrypt sql generujący tabelki dla użytku Quartz.net znajdziemy w folderze database w plikach ściągniętych z strony biblioteki.

Dodatkowo w Global.axax startujemy naszego Schedulera:

void Application_Start(object sender, EventArgs e)
{
Scheduler.GetScheduler().Start();
}

można również wyłączać go gdy aplikacja się zamyka.

void Application_End(object sender, EventArgs e)
{
Scheduler.GetScheduler().Shutdown();
}

A oto najważniejsze dodawanie zadań do schedulera:

DateTime SelectedDate = this.Calendar1.SelectedDate;
int hour = Convert.ToInt32(this.tbHour.Text);
int minute = Convert.ToInt32(this.tbMinute.Text);
int second = Convert.ToInt32(this.tbSecond.Text) – 1;
DateTime startTime = new DateTime(SelectedDate.Year, SelectedDate.Month, SelectedDate.Day, hour, minute, second, DateTimeKind.Local);

DateTimeOffset runTime = DateBuilder.EvenSecondDate(startTime);

IJobDetail job = JobBuilder.Create<Job>()
.WithIdentity(“job” + Guid.NewGuid().ToString(), “group1″)
.WithDescription(“will run at: ” + runTime.ToString())
.Build();

ISimpleTrigger trigger = (ISimpleTrigger)TriggerBuilder.Create()
.WithIdentity(“trigger” + Guid.NewGuid().ToString(), “group1″)
.StartAt(runTime)
.WithSchedule(SimpleScheduleBuilder.Create())
.Build();

Scheduler.GetScheduler().ScheduleJob(job, trigger);

Używając kontrolki kalendarza pobieram datę, a z texboxów czas wywołania zadania. Używam DateTimeKind.Local aby dostać datę w formacie lokalnym, a następnie za pomocą DateBuilder dostaje datę jako DateTiemOffset, którego oczekuje trigger.
Tworzymy zadanie(Job) z unikalną nazwą i grupą. Jeśli nazwa(key) nie będzie unikalna nie zostanie ona dodana do kolejki, dlatego ja używam Guid-a.
Kolejna sprawa to stworzenie triggera, który wywoła nasze zadanie o konkretnym czasie. Na samym końcu dodajemy zadanie i trigger do schedulera.
Jeśli scheduler został włączony w Global.asax to nasze zadanie zostanie przetwarzane automatycznie , w przeciwnym razie należy włączyć scheduler.

Mam nadzieję, że pomogłem w początkach z biblioteką. Sam co dopiero zaczynam się bawić nią ;)

Follow

Otrzymuj każdy nowy wpis na swoją skrzynkę e-mail.