Farklı .Net Versiyonları İle Derleme

by Timur 26. Ocak 2012 18:43

Merhaba.

.Net ile hazırladığımız bir projeyi birden fazla versiyon ile derlemek isteyebiliriz. Bunun için her seferinde versiyonunu değiştirmek yerine daha kolay bir yöntem olarak msbuild kullanabiliriz.

msbuild'e konfigrasyon dokümanı olarak projenin .csproj dosyasını verebiliriz. Örneğin kendisi .net 4.0 olan RSATest projesinin .net 2.0 versiyonu için derlenmek istediğimizde "Visual Studio Command Prompt" a

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC>msbuild G:\Tests\RsaTest\RsaTest\RsaTest.csproj /p:TargetFrameworkVersion=v2.0

yazmamız yeterli. Burada dikkat edeceğimiz bir konu ise derlenecek uygulamanın derlenecek versiyona aykırı referans veya kod olmaması. Örneğin .net 2.0 ile derlemek istediğimiz bir uygulamanın System.Linq kullanmamış olması gerekiyor.

Derleme işleminde farklı parametreler de kullanabiliriz.. Örneğin derleme sonucunda oluşan dosyaları istediğimiz klasöre /p:outputpath={dir} diyerek verebiliriz. Veya projenin release derlenmesi için /p:Configuration=Release dememiz yeterli.

C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC>msbuild G:\Tests\RsaTest\RsaTest\RsaTest.csproj /p:TargetFrameworkVersion=v2.0 /t:rebuild /p:Configuration=Release /p:outputpath=C:\RsaTest

İyi günler herkese...

Tags: , ,

WPF ile DigitTextBox

by Timur 10. Ocak 2012 17:58

Öncelikle herkese merhaba.

Herşeyin başı merak dedim ve hiç lazım olmadığı halde WPF içerisinde CustomControl yazma işine merak sardım. Bu merak sonucundaki ilk çıktıyı da paylaşmak istedim. İlk denemem sadece sayıların girilebileceği bir DigitTextBox olacak.

Bunun için yaratacağımız sınıfı "Control" sınıfından türetiyoruz. Ayrıca bu kontrolümüzü dizayn edebilmek için "Themes" klasörünün altına "Generic.xaml" dosyasını oluşturup dizayn işlemine başlıyoruz.

 

Yazacağımız digittextbox için bir adet textbox, iki adet de butona ihtiyacımız var. Bunları görmek istediğimiz şekilde "Generic.xaml" dosyasında tanımlıyoruz.

 

<ResourceDictionary

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:DebugControls.DebugDigitTextbox">

    <Style TargetType="{x:Type local:DebugDigitTextBox}">

        <Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="{x:Type local:DebugDigitTextBox}">

                    <Border Background="{TemplateBinding Background}"

                            BorderBrush="{TemplateBinding BorderBrush}"

                            BorderThickness="{TemplateBinding BorderThickness}">

						<Grid>

							<Grid.ColumnDefinitions>

								<ColumnDefinition Width="*"></ColumnDefinition>

								<ColumnDefinition Width="Auto"></ColumnDefinition>

							</Grid.ColumnDefinitions>

							<TextBox x:Name="TemplateTextbox" Grid.Column="0"></TextBox>

							<Grid Grid.Column="1">

								<Grid.RowDefinitions>

									<RowDefinition></RowDefinition>

									<RowDefinition></RowDefinition>

								</Grid.RowDefinitions>

								<Button x:Name="IncrementButton" Grid.Row="0"

										Margin="1,1,1,1">

									<TextBlock                                                    

                                        Text="p"                                                    

                                        FontFamily="Wingdings 3"

                                        FontSize="6"

                                        HorizontalAlignment="Center"

                                        VerticalAlignment="Center"/>

								</Button>

								<Button x:Name="DecrementButton" Grid.Row="1"

										Margin="1,1,1,1">

									<TextBlock                                                    

                                        Text="q"                                                    

                                        FontFamily="Wingdings 3"

                                        FontSize="6"

                                        HorizontalAlignment="Center"

                                        VerticalAlignment="Center"/>

								</Button>

							</Grid>

						</Grid>

					</Border>

                </ControlTemplate>

            </Setter.Value>

        </Setter>

    </Style>

</ResourceDictionary>
 
Daha sonra ilk yapacağımız işlerdne biri Template Apply olduğu anda kontrolümüzdeki textbox ve butonları alıp, gerekli eventleri yakalamak.
 
public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			if (_IncrementButton != null)
			{
				_IncrementButton.Click -= _IncrementButtonClick;
			}

			if (_DecrementButton != null)
			{
				_DecrementButton.Click -= _DecrementButtonClick;
			}

			if (_TextBox != null)
			{
				_TextBox.PreviewTextInput -= _TextBoxKeyDown;
				_TextBox.TextChanged -= _TextBoxTextChanged;
				_TextBox.PreviewKeyDown -= _TextBoxPreviewKeyDown;
			}

			_TextBox = GetTemplateChild(TextboxName) as TextBox;
			_IncrementButton = GetTemplateChild(IncrementButtonName) as Button;
			_DecrementButton = GetTemplateChild(DecrementButtonName) as Button;

			_IncrementButton.Click += _IncrementButtonClick;
			_DecrementButton.Click += _DecrementButtonClick;
			_TextBox.PreviewTextInput += _TextBoxKeyDown;
			_TextBox.TextChanged += _TextBoxTextChanged;
			_TextBox.PreviewKeyDown += _TextBoxPreviewKeyDown;
		}

Diğer önemli bir nokta ise kontrolün static constructor ında base sınıftan gelen "DefaultKeyStyle" ı override etmek.

 

static DebugDigitTextBox()

{

	DefaultStyleKeyProperty.OverrideMetadata(typeof(DebugDigitTextBox), new FrameworkPropertyMetadata(typeof(DebugDigitTextBox)));

}

 

Geriye kalan ise eventleri yakalayıp, gerekli işlemleri yapabilmek...

 

public class DebugDigitTextBox : Control

	{

		#region Events

		public event RoutedPropertyChangedEventHandler<TimeSpan> ValueChanged

		{

			add { base.AddHandler(ValueChangedEvent, value); }

			remove { base.RemoveHandler(ValueChangedEvent, value); }

		}



		public static readonly RoutedEvent ValueChangedEvent =

			EventManager.RegisterRoutedEvent(

			"ValueChanged",

			RoutingStrategy.Bubble,

			typeof(RoutedPropertyChangedEventHandler<int>),

			typeof(DebugDigitTextBox));

		#endregion



		#region Members

		TextBox _TextBox;

		Button _IncrementButton;

		Button _DecrementButton;



		const string TextboxName = "TemplateTextbox";

		const string IncrementButtonName = "IncrementButton";

		const string DecrementButtonName = "DecrementButton";

		#endregion



		#region DependencyProperties

		public static DependencyProperty ValueProperty = DependencyProperty.Register(

			"Value", typeof(int), typeof(DebugDigitTextBox), new PropertyMetadata(0, DebugDigitTextBox.OnValueChanged, null));

		#endregion



		#region Properties

		public int Value

		{

			get { return Convert.ToInt32(GetValue(ValueProperty)); }

			set

			{

				if (Convert.ToInt32(GetValue(ValueProperty)) == value)

					return;

				SetValue(ValueProperty, value);

			}

		}

		#endregion



		#region Initialization

		static DebugDigitTextBox()

		{

			DefaultStyleKeyProperty.OverrideMetadata(typeof(DebugDigitTextBox), new FrameworkPropertyMetadata(typeof(DebugDigitTextBox)));

		}



		public DebugDigitTextBox()

		{

			Value = 0;

			Width = 100;

		}

		#endregion



		#region Methods

		private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)

		{

			DebugDigitTextBox textBox = obj as DebugDigitTextBox;

			if (textBox != null)

			{

				textBox.OnValueChanged((int)e.OldValue, (int)e.NewValue);

			}

		}



		void _IncrementButtonClick(object sender, RoutedEventArgs e)

		{

			Value++;

		}



		void _DecrementButtonClick(object sender, RoutedEventArgs e)

		{

			Value--;

		}



		void _TextBoxKeyDown(object sender, TextCompositionEventArgs e)

		{

			if (e.Text.Length == 1)

			{

				if (e.Text[0] == '-')

				{

					Value = Value * -1;

					e.Handled = true;

					return;

				}

				else if (!char.IsDigit(e.Text[0]))

				{

					e.Handled = true;

					return;

				}

			}

		}



		void _TextBoxTextChanged(object sender, TextChangedEventArgs e)

		{

			if (string.IsNullOrEmpty(_TextBox.Text))

			{

				Value = 0;

			}

			else if (_TextBox.Text == "-")

			{

				Value = 0;

			}

			else

			{

				try

				{

					Value = Convert.ToInt32(_TextBox.Text);

				}

				catch

				{

					_TextBox.Text = _TextBox.Text.Remove(_TextBox.SelectionStart - 1, 1);

					e.Handled = true;

					return;

				}

			}

			if (Value == 0)

			{

				_TextBox.Text = Value.ToString();

			}

		}



		void _TextBoxPreviewKeyDown(object sender, KeyEventArgs e)

		{

			if (e.Key == Key.Down)

			{

				Value = Value - 1;

			}

			else if (e.Key == Key.Up)

			{

				Value = Value + 1;

			}

		}



		protected virtual void OnValueChanged(int oldValue, int newValue)

		{

			_TextBox.Text = Value.ToString();

			RoutedPropertyChangedEventArgs<int> e = new RoutedPropertyChangedEventArgs<int>(oldValue, newValue);

			e.RoutedEvent = ValueChangedEvent;

			base.RaiseEvent(e);

		}



		public override void OnApplyTemplate()

		{

			base.OnApplyTemplate();



			if (_IncrementButton != null)

			{

				_IncrementButton.Click -= _IncrementButtonClick;

			}



			if (_DecrementButton != null)

			{

				_DecrementButton.Click -= _DecrementButtonClick;

			}



			if (_TextBox != null)

			{

				_TextBox.PreviewTextInput -= _TextBoxKeyDown;

				_TextBox.TextChanged -= _TextBoxTextChanged;

				_TextBox.PreviewKeyDown -= _TextBoxPreviewKeyDown;

			}



			_TextBox = GetTemplateChild(TextboxName) as TextBox;

			_IncrementButton = GetTemplateChild(IncrementButtonName) as Button;

			_DecrementButton = GetTemplateChild(DecrementButtonName) as Button;



			_IncrementButton.Click += _IncrementButtonClick;

			_DecrementButton.Click += _DecrementButtonClick;

			_TextBox.PreviewTextInput += _TextBoxKeyDown;

			_TextBox.TextChanged += _TextBoxTextChanged;

			_TextBox.PreviewKeyDown += _TextBoxPreviewKeyDown;

		}

		#endregion

	}

 

İyi çalışmalar herkeslere...

Tags: , , ,

OperationInvoker ile Tüm Operasyonları İzleme

by Timur 1. Aralık 2011 16:21

Merhaba.

Bugün OperationInvoker'dan biraz bahsedeceğim. WCF'de IOperationInvoker'dan türetme yolu ile yeni Invoker yaratabilir, WCF servis tarafında istediğimiz operasyonların çalışmalarını istediğimiz gibi yönlendirebiliriz. Örneğin bir OperatıonInvoker yazarak istediğimiz bir kontrolü yapabiliriz. WCF servisimizde çalışan tüm operasyonları loglamak istemez miyiz?

 

Bunun için bir OperatinBehaviour yazarak ilgili operasyonun Invoker sınıfına kendi oluşturduğumuz sınıfı atıyoruz.

public class ExceptionOperationBehavior : Attribute, IOperationBehavior

{

	public void AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

	{

		

	}



	public void ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation)

	{

		

	}



	public void ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)

	{

		IOperationInvoker invoker = dispatchOperation.Invoker;

		dispatchOperation.Invoker = new ExceptionOperationInvoker(invoker, operationDescription.Name);

	}



	public void Validate(OperationDescription operationDescription)

	{

		

	}

}

 

Kendi oluşturduğumuz sınıfımız ise methodun kendi Invoker'ını çalıştıracak, fakat bu çalıştırma öncesinde ve sonrasında gerekli loglamaları yapacaktır.

 

public class ExceptionOperationInvoker : IOperationInvoker

{

	#region Members

	IOperationInvoker _Invoker = null;

	string _Name = string.Empty;

	#endregion



	#region Properties

	public bool IsSynchronous

	{

		get { return _Invoker.IsSynchronous; }

	}

	#endregion



	#region Initialization

	public ExceptionOperationInvoker(IOperationInvoker invoker, string name)

	{

		_Invoker = invoker;

		_Name = name;

	}

	#endregion



	#region Methods

	public object[] AllocateInputs()

	{

		return _Invoker.AllocateInputs();

	}



	public object Invoke(object instance, object[] inputs, out object[] outputs)

	{

		string userName = string.Empty;

		if (OperationContext.Current != null)

		{

			if (ServerManager.Instance.Connecteds.Contains(OperationContext.Current.SessionId))

			{

				userName = ServerManager.Instance.Connecteds[OperationContext.Current.SessionId].Username;

			}

		}

		try

		{

			object obj = _Invoker.Invoke(instance, inputs, out outputs);

			LogEntry entry = new LogEntry(userName, _Name + " operation completed successfully", DateTime.Now, LogEntryType.Information);

			ServerManager.Instance.LogManager.Add(entry);

			return obj;

		}

		catch (Exception exp)

		{

			ServerManager.Instance.LogManager.Add(exp, userName);

			throw new FaultException(new FaultReason(exp.Message));

		}

	}



	public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)

	{

		return _Invoker.InvokeBegin(instance, inputs, callback, state);

	}



	public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)

	{

		return _Invoker.InvokeEnd(instance, out outputs, result);

	}

	#endregion



}

 

Eğer bunu WCF'te çalışan tüm operasyonlar için yapmak istiyorsak da, yapacağımız bir ServiceBehaviour yazarak servis ayağa kalktığında tüm operasyonlar için gerçekleştirmektir.

public class ExceptionServiceBehavior : Attribute, IServiceBehavior

{

	public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

	{

		

	}



	public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

	{

		foreach (ServiceEndpoint endPoint in serviceDescription.Endpoints)

		{

			foreach (OperationDescription description in endPoint.Contract.Operations)

			{

				description.Behaviors.Add(new ExceptionOperationBehavior());

			}

		}

	}



	public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)

	{

		return;

	}

}

 

Servisimize de Attribute olarak eklediğimizde işlem tamam demektir...

[ExceptionServiceBehavior]

public class SosGame : ISosGame

{

}

 

Hoşçakalın...

Tags: , , ,

PrintPreviewDialog'da Yazıcı Ayarları Yapma

by Timur 24. Kasım 2011 09:51

Merhaba.

PrintPreviewDialog içerisinde yazıcı ayarlarını yapabilmek için, bu dialog içerisindeki ToolStrip'e erişip, istediğimiz bir ToolStripButton'u koyabiliriz. Bu ToolStripButton'un click event'inde de gerekli ayarlamaları yapabiliriz.

ToolStripButton button = new ToolStripButton();
button.Height = 22;
button.Click += new EventHandler(button_Click);
button.Text = Properties.Resources.Txt_PrinterSettings;

PrintPreviewDialog dialog = new PrintPreviewDialog();
((Form)dialog).WindowState = FormWindowState.Maximized;
for (int i = 0; i < dialog.Controls.Count; i++)
{
	if (dialog.Controls[i] is ToolStrip)
	{
		ToolStrip strip = dialog.Controls[i] as ToolStrip;
		strip.Items.Add(button);
		break;
	}
}
PrintDocument document = new PrintDocument();
document.PrintPage += new PrintPageEventHandler(document_PrintPage);
dialog.Document = document;
dialog.ShowDialog();
button.Click -= new EventHandler(button_Click);
button.Dispose();
button = null;

 

void button_Click(object sender, EventArgs e)
{
	PrintPreviewDialog preview = ((ToolStripButton)sender).GetCurrentParent().Parent as PrintPreviewDialog;
	if (preview != null)
	{
		using (PrintDialog dialog = new PrintDialog())
		{
			dialog.PrinterSettings.PrinterName = preview.Document.PrinterSettings.PrinterName;
			if (dialog.ShowDialog() == DialogResult.OK)
			{
				preview.Document.PrinterSettings.Copies = dialog.PrinterSettings.Copies;
				preview.Document.PrinterSettings.PrinterName = dialog.PrinterSettings.PrinterName;
			}
		}
	}
}

Tags: , ,

.Net Assembly'lerinden Image ve Icon'lar Kaydetme

by Timur 18. Kasım 2011 18:57

Birçoğumuzun başına gelmiştir. Bir program yazarız, tam da bu program için güzel bir icon veya image buluruz, webden veya bir arkadaştan. Gel zaman git zaman yine bir gün bilgisayar başında hangi image ve icon kullanacam diye kara kara düşünürken geçmiştekiler gelir aklımıza. Önceki kullandığımız image veya icon'lar geçer gözümüzün önünden bir film şeridi gibi. Peki bu anda ya o image veya icon'ların nereye gittiği belli değilse? Ya da eski bir diskle, ya da artık kullanılmayan bir maille tarihin derinliklerine gömüldüyse? İşte amacımız elimizdeki .dll veya .exe'den image ve icon'ları alabilmek.

 

İlk yapacağımız seçilen dosyanın assembly bilgisini almak ve içindeki resource ları almak. Sonrasında ise bu resource ları teker teker çözümlemek.

Assembly assembly = Assembly.LoadFile(fileName);
string[] names = assembly.GetManifestResourceNames();
for (int i = 0; i < names.Length; i++)
{
ManifestResourceInfo info = assembly.GetManifestResourceInfo(names[i]);
	ResourceContainer container = new ResourceContainer(names[i]);
	container.Resolve(assembly, names[i]);
cbResources.Items.Add(container);
}

public void Resolve(Assembly assembly, string name)
{
_Items = new List<IResourceItem>();
	Stream resourceStream = assembly.GetManifestResourceStream(name);
	if (resourceStream == null)
		return;
	ManifestResourceInfo info = assembly.GetManifestResourceInfo(name);
	if (((info.ResourceLocation & ResourceLocation.Embedded) == ResourceLocation.Embedded) && name.EndsWith(".resources"))
		{
			ResourceReader reader = new ResourceReader(resourceStream);
			IDictionaryEnumerator enumerator = reader.GetEnumerator();
			while (enumerator.MoveNext())
			{
				string type = string.Empty;
				string key = enumerator.Key.ToString();
				byte[] values = null;
				reader.GetResourceData(key, out type, out values);
				List<IResourceItem> items = ResourceItem.GetResourceItem(key, enumerator.Value, resourceStream);
				if (items != null)
				{
					for (int i = 0; i < items.Count; i++)
					{
						Items.Add(items[i]);
					}
				}
			}
		}
	}

Daha sonrasında ise çözümlenen bu resource lardan işimize yarayacak bilgileri alıyoruz.

public static List<IResourceItem> GetResourceItem(string name, object value, Stream stream)
{
	if (value is string)
	{
		StringResourceItem item = new StringResourceItem(name, value);
		return new List<IResourceItem>() { item };
	}
	else if (value is Icon)
	{
		IconResourceItem item = new IconResourceItem(name, value);
		return new List<IResourceItem>() { item };
	}
	else if (value is ImageListStreamer)
	{
		List<IResourceItem> items = new List<IResourceItem>();
		using (ImageList list = new ImageList())
		{
			list.ImageStream = value as ImageListStreamer;
			int index = 0;
			foreach (Image image in list.Images)
			{
				items.Add(new ImageResourceItem(name, image, index));
				index++;
			}
		}
		return items;
	}
	else if (value is Image)
	{
		ImageResourceItem item = new ImageResourceItem(name, value, -1);
		return new List<IResourceItem>() { item };
	}
	else
	{
		object v = value;
	}

	return null;
}
 

Tek tek image ve icon'larla uğraşamam ben arkadaş diyenler de "Save All Images" dediğinde tüm image ve icon'lar seçilen klasöre kaydediliyor.

private void _SaveAllImages()
{
	if (cbResources.Items.Count == 0)
	{
		_LoadResources();
	}
	lblMessage.Text = "Saving...";
	Application.DoEvents();
	using (FolderBrowserDialog dialog = new FolderBrowserDialog())
	{
		if (!string.IsNullOrEmpty(Properties.Settings.Default.DefaultSavePath))
		{
			dialog.SelectedPath = Properties.Settings.Default.DefaultSavePath;
		}
		if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
		{
			Properties.Settings.Default.DefaultSavePath = dialog.SelectedPath;
			Properties.Settings.Default.Save();
			string iconDirectory = dialog.SelectedPath + "\\Icons";
			string imageDirectory = dialog.SelectedPath + "\\Images";
			if (!Directory.Exists(iconDirectory))
				Directory.CreateDirectory(iconDirectory);
			if (!Directory.Exists(imageDirectory))
				Directory.CreateDirectory(imageDirectory);
			for (int resourceIndex = 0; resourceIndex < cbResources.Items.Count; resourceIndex++)
			{
				ResourceContainer container = cbResources.Items[resourceIndex] as ResourceContainer;
				string[] names = container.Name.Split(new char[] { '.' });

				for (int itemIndex = 0; itemIndex < container.Items.Count; itemIndex++)
				{
					IResourceItem item = container.Items[itemIndex];
					string fileName = string.Empty;
					if (item.ResourceType == ResourceType.Icon)
					{
						IconResourceItem resourceItem = item as IconResourceItem;
						if (resourceItem.Icon != null)
						{
							if (names.Length > 1)
							{
								fileName = dialog.SelectedPath + "\\Icons\\" + names[names.Length - 2] + ".ico";
								using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write))
								{
											resourceItem.Icon.Save(fs);
									fs.Flush();
								}
							}
						}
					}
					else if (item.ResourceType == ResourceType.Image)
					{
						ImageResourceItem imageItem = item as ImageResourceItem;
						if (imageItem.Image != null)
						{
							string name = imageItem.Name;
							if (imageItem.Index != -1)
							{
								name = imageItem.Index.ToString();
							}
							fileName = dialog.SelectedPath + "\\Images\\" + names[names.Length - 2] + "_" + imageItem.Width.ToString() + "_" + name + ".png";
							imageItem.Image.Save(fileName, ImageFormat.Png);
						}
					}
				}
			}
			MessageBox.Show("Operation Finished", "Success", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
		}
	}
	lblMessage.Visible = false;
}
 

Kaynak kodları indirmek için : http://www.codeproject.com/KB/dotnet/ExtractImagesIcons.aspx  

Esen kalın...

Tags: , ,

Selamlar

by Timur 18. Kasım 2011 18:53

Askerlik sürecinde kaybettiğim alan adımı tekrar almanın sevincini yaşıyorum :)

Demek ki neymiş, herşeyi son güne bırakmamak lazımmış... Toplum olarak gerçeğimiz bu ne yazık ki, alışkanlıklardan kolay vazgeçilmiyor...

Umarım bu kez daha uzun soluklu, daha dolu bir blog olur.

 

Herkeslere selamlar, saygılar...

Tags:

Hakkımda

Evli, mutlu, çocuksuz...

Bilgisayar Mühendisi...

.Net Developer...

Crs Soft