Creating LAN messenger using WCF Message Inspector

WCF self-hosting service can be really useful for messaging within the single or multiple tiers. Communication may involve sending execution tasks to other components or sending simple messages to be displayed on remote computer.

When sending system massages we can use Message Queuing (MSMQ) and transactions to ensure that the task will be executed even if the service if temporary off-line by storing it in the queue for planned execution. This approach makes the system loosely coupled to it’s components.

Self hosted WCF service can also be used to send and receive simple messages. In this article I will show you how to implement simple LAN messenger.

wcfLMessenger1

The interesting feature of WCF services is the ability to intercept messages as they arrive through the message channel. By using Message Inspector we can preview or change the message that we have intercepted.
We can hook in our Message Inspector by creating and adding custom service behavior. Lets start with the MyServiceBehavior class:

  public class MyServiceBehavior : IServiceBehavior
   {
      public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, 
           System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
       {
       }

      public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
      {
           //note that ChannelDispatchers are available only after service is open
          foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) 
          {
             foreach (EndpointDispatcher epDisp in channelDispatcher.Endpoints)
             {
                   //attach message interceptor
                   epDisp.DispatchRuntime.MessageInspectors.Add(new MyMessageInspector()); 
               }
            }
       }

       public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
       {
       }
   }

Now we need to create Message Interceptor class that will intercept the massages and in our case send it to the UI to be displayed. Code below simply extracts the message value from the SOAP message received.

 public class MyMessageInspector : IDispatchMessageInspector
 {
	public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel, InstanceContext instanceContext)
	{
		foreach (var form in Application.OpenForms)
		{
			if (form is ClientServiceForm)
			{
				var mainForm = form as ClientServiceForm;

				var message = request.ToString();

				//simple string extraction (we assume only one interface method)
				var startPos = message.LastIndexOf("<value>");
				var endPos = message.LastIndexOf("</value>");
				message = message.Substring(startPos + 7, endPos - startPos - 7);

				mainForm.AddMessage("\n" + message, ""); //send message to be displayed

				return null;
			}
		}
		return null;
  }

Next step is to start self-hosted service when application starts. Code below creates ServiceHost instance, adds TCP endpoint and our custom behavior class. Please note that service will remain open as long as the window Form is not closed (Windows Service wcf host should be implemented instead if required).

 void StartService()
 {
    var baseAddress = new Uri(HostEndPointAddress);

    hostService = new ServiceHost(typeof(MessageService), baseAddress);

    hostService.AddServiceEndpoint(typeof(IServiceHost), new NetTcpBinding(), "");
    hostService.Description.Behaviors.Add(new WCFClient.AppService.MyServiceBehavior());

    hostService.Open();

    txtClientReceivedMessage.AppendText(string.Format("The Service started at {0}", baseAddress));
}

Now is the time to implement sending messages to the remote endpoint. We will use ChannelFactory to instantiate new service that will send the messages. Please note that we have to send it to other thread in order to avoid our main UI thread to function properly.

 private void btnSendMessage_Click(object sender, EventArgs e)
 {
    if (txtClientInputMessage.Text.Trim().Length == 0)
    {
        MessageBox.Show("Enter message to be sent!"); return;
    }

    //send it to other thread
    var wcfChannelThread = new Thread(new ParameterizedThreadStart(SendMessage));
    wcfChannelThread.Start(new List<string>() { txtClientInputMessage.Text, txtTargetEndPoint.Text });

    txtClientInputMessage.Text = "";//clear input box
}


 void SendMessage(object config)
 {
    var arg = config as List<string>;
    try
    {
        //use TCP when sending message on the same computer or within the trusted network
        var binding = new NetTcpBinding();
        var endpoint = new EndpointAddress(arg[1]);

        var factory = new ChannelFactory<IServiceHost>(binding, endpoint);
        var channel = factory.CreateChannel();

        //send entered message using created channel
        channel.SendMessage(Convert.ToString(arg[0])); 

        //send message to UI
        AddMessage("", arg[0]);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

The last thing to do is to create thread save function that will update our UI rich-text box control with the messages coming in from background threads:

 public void AddMessage(string messageReceived, string messageSent)
 {
    if (this.InvokeRequired)
    {
        BeginInvoke(new AddMessageDel(AddMessage), new object[] { messageReceived, messageSent });
    }
    else
    {
        //insert message
        if (messageReceived.Trim().Length > 0)
        {
            txtClientReceivedMessage.SelectionFont = new System.Drawing.Font("Tahoma", 11);
            txtClientReceivedMessage.SelectionColor = Color.Red;
            txtClientReceivedMessage.AppendText(string.Format("{0}", messageReceived));
        }
        else
        {
            //sent by me
            txtClientReceivedMessage.SelectionFont = new System.Drawing.Font("Tahoma", 11);
            txtClientReceivedMessage.SelectionColor = Color.Green;
            txtClientReceivedMessage.AppendText(string.Format("\nme: {0}", messageSent));
        }

        //insert date
        txtClientReceivedMessage.SelectionFont = new System.Drawing.Font("Tahoma", 8);
        txtClientReceivedMessage.SelectionColor = Color.Blue;
        txtClientReceivedMessage.AppendText(string.Format(" [sent at: {0}]", DateTime.Now.ToString("HH:mm:ss")));

        //scroll to bottom
        txtClientReceivedMessage.SelectionStart = txtClientReceivedMessage.Text.Length;
        txtClientReceivedMessage.ScrollToCaret();
    }
}

I have included two separate project solutions. Each project represents client endpoint that can be used to send message to the other. When using the code, firewall warning message could be displayed – please allow it to listen on chosen port.

If you want to send a message to other computer within the local network, please change the endpoint address to following format: net.tcp://endpointComputerIP:82/client2. If you want to send message to other computer outside of your network, please change binding to http or https.

Hope I have inspired you to explore all possibilities of WCF framework 🙂

WCF_LAN_Messenger

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

WebApi requests compression using Ext.js and asp.net Mvc4

One of the easiest ways to maximize performance of ajax requests being sent over the network is to use gzip compression. There is already a built in mechanism for compressing requests but it depends on IIS settings of our hosting provider. In our case we will apply custom Delegate Handler using asp.net mvc4 ApiController that is not dependent on any environment specific settings.

If you are wondering if it’s worth to implement this solution, have a look on my test results below:

compression

Depending on the content being sent, we can reduce the request size by an average 50% or more. On the above image, the first request have been compressed while the second one was not.

In order to test our solution we will simply invoke ajax request from Ext.js application running along with self-hosted asp.net mvc4 webapi.

  Ext.Ajax.request({
            method: 'GET',
            url: 'api/ExtMvcTest/',

            success: function (response) {
                var text = response.responseText;
                alert('Request completed data length: ' + text.length);
            },
            failure: function (response) {
                alert('An error occured!');
            }
        });

On the server site we need to create our handler that derives from DelegatingHandler class. By doing that we override http SendAsync function and implement our custom CompressedContent class.

 public class APICompressionDelegateHandler : DelegatingHandler
 {
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
        {
            HttpResponseMessage response = responseToCompleteTask.Result;

            if (response.RequestMessage.Headers.AcceptEncoding != null &&
                response.RequestMessage.Headers.AcceptEncoding.Count > 0)
            {
                string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value;

                if (response.Content != null)
                {
                    response.Content = new CompressedContent(response.Content, encodingType);
                }
            }

            return response;
        },
        TaskContinuationOptions.OnlyOnRanToCompletion);
    }
}

Next, we need to override SerializeToStreamAsync function in our CompressedContent object that inherits from HttpContent class to implement actual compression and send it back to the calling delegate.

 protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
  {
        Stream compressedStream = null;

        if (encodingType == "gzip")
        {
            compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
        }
        else if (encodingType == "deflate")
        {
            compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
        }

        return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
        {
            if (compressedStream != null)
            {
                compressedStream.Dispose();
            }
        });
   }

Last thing do to is to register our delegate at application start-up and to define our target controller we get our data from.

 public static class WebApiConfig
  {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                          name: "ExtTest",
                          routeTemplate: "api/ExtMvcTest/",
                          defaults: new { controller = "ExtMvcTest", action = "GetTestData" });

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "Controllers/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
 
           config.MessageHandlers.Add(new APICompressionDelegateHandler()); //enable API requests compression
        }
    }

This is how our solution files look like:
mvc4_compr_solution

I have added fully working project example below. Please be aware that using compression may overload your server if used improperly. It is advisable to use it mainly for dashboards where the response time is the highest priority.
ExtMvc

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Generating static map images using Google Map API

If you are using static map images on your website and have a lot of different addresses to be displayed, you can easily automate the process of creating them. The code below will request Google map API and download static images. The address list is stored in .csv file but can also be pulled directly from database. Once map images are created, you can recreate them with different params again.

There is a limit for generating static images without api key, so make sure you wait a bit when getting forbidden error from Google request.

SW1A-0AA


public static string rootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        public static string csvPath = rootPath + "\\Addresses.csv";
        public static Random rnd = new Random();
 
        public static void Main()
        {
            using (var sr = new StreamReader(csvPath))
            {
                while (!sr.EndOfStream)
                {
                    var data = sr.ReadLine().Split('|');
                    var address = data[0];
                    var postCode = data[1];
 
                    SaveFile(address, postCode);
                }
            }
 
            Console.WriteLine("done!");
            Console.ReadLine();
        }
 
        public static void SaveFile(string address, string postCode)
        {
            try
            {
                var filePath = new DirectoryInfo(rootPath).Parent.Parent.Parent.FullName + @"\output\" + postCode + ".jpg";
                if (File.Exists(filePath))
                {
                    Console.WriteLine("skipped " + address + " " + postCode + " (image exists in output directory)"); return;
                }
 
                Console.WriteLine("processing " + address + " " + postCode);
 
                using (var wc = new WebClient())
                {
                    var url = "https://maps.google.com/maps/api/staticmap?address=" + address + "&center=" + postCode + "&size=275x275&format=jpg&maptype=roadmap&zoom=15.5&markers=color:0xE21A9B|label:T|" + postCode + "&sensor=false";
                    wc.DownloadFile(url, new DirectoryInfo(rootPath).Parent.Parent.Parent.FullName + @"\output\" + postCode + ".jpg");
                }
 
                Thread.Sleep(rnd.Next(300, 2300)); //wait a bit
            }
            catch (Exception ex)
            {
                Console.WriteLine("error for: " + address + " | " + postCode + Environment.NewLine + ex.Message);
            }
        }
    }

Download project: googleImageApi

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

KnockoutJS bindings and observables

KnockoutJS it’s the JavaScript implementation of the MVVM pattern that is based on native JavaScript event management. Implementing knockoutJS both as a standalone solution or in conjunction with other web technologies can greatly improve user experience in our web applications.

As a example lets create simple list of places and restaurants as below:
knockoutjs

In our example, places can have multiple related restaurants. To make programming more funny lets create two separate view models for our entities. In order to differentiate two models we need to use overloaded applyBindings methods passing in form elements.

 var PVM = new PlacesViewModel();
 var RVM = new RestaurantViewModel()

  ko.applyBindings(PVM, document.getElementById('placesModel'));
  ko.applyBindings(RVM, document.getElementById('restaurantModel'));

Lets define html form for places and it’s template:

  <div id="placesModel" style="float: left; width: 350px;">
        <h2>
            Places</h2>
        <div data-bind="foreach: places">
            <div style="background-color: #e5dfdf;" data-bind="template: { name: 'place-template', data: $data }">
            </div>
        </div>
        <br />
        <br />
        <hr />
        <form data-bind="submit: addPlace">
        Name:
        <input name="name" /><br />
        <br />
        Details:
        <textarea rows="4" name="details" cols="20"></textarea>
        <button type="submit">
            add place</button>
        </form>
    </div>

    //template for places
   <script type="text/html" id="place-template">
        <h3 style="color: green;"><b data-bind="visible: !editing(), text: name, click: $root.edit">&nbsp;</b>
            <input data-bind="visible: editing, value: name, hasfocus: editing" />
        </h3>
        <p><em>Click the name above to edit</em></p>

        <p>Place details: <span data-bind="text: details"></span></p>
        <span data-bind="text: $data.name"></span>&nbsp;<a href="#" data-bind="click: $root.removePlace">Remove</a>

        <h4><span data-bind="text: name"></span>&nbsp;restaurants</h4>

        <ul data-bind="foreach: RVM.restaurants">
            <!--ko if: $data.place.toLowerCase().indexOf($parent.name().toLowerCase()) >= 0 -->
            <li style="">
                <span data-bind="text: $data.name"></span>&nbsp;<a href="#" data-bind="click: RVM.removeRestaurant">Remove</a>
            </li>
            <!--/ko-->
        </ul>
    </script>

At this point we need to define knockout view model and define functions for data binding and observables:

 function PlacesViewModel() {
            var self = this;

            self.places = ko.observableArray([
               { name: ko.observable('London'), details: '....', editing: ko.observable(false) },
            ]);

            self.selectedPlace = ko.observable({ name: ko.observable('London'), details: '....', editing: ko.observable(false) });

            self.removePlace = function (place) {
                self.places.remove(place)
            }

            self.addPlace = function (placeForm) {
                //basic validation
                if (placeForm.name.value.trim().length == 0) {
                    alert('Type the place name!'); return;
                }

                var place = { name: ko.observable(placeForm.name.value), details: placeForm.details.value, editing: ko.observable(false) }
                self.places.push(place);

                placeForm.name.value = ''; placeForm.details.value = ''; //clear form
            }

            self.edit = function (arg) {
                arg.editing(true)
            }
        }

Now, lets create simple restaurant form and controls:

 <div id="restaurantModel" style="float: left; padding-left: 30px;">
        <h2>
            Restaurant list</h2>
        <ul data-bind="foreach: restaurants">
            <li style=""><span data-bind="text: $data.name"></span>&nbsp;(<i><span data-bind="text: $data.place"></span></i>)&nbsp;<a
                href="#" data-bind="click: $parent.removeRestaurant">Remove</a> </li>
        </ul>
        <br />
        <br />
        <hr />
        <form data-bind="submit: addRestaurant">
        Name:
        <input name="name" /><br />
        <br />
        Place:
        <select data-bind="options: PVM.places, optionsValue:'name', optionsText: 'name'"
            name="place">
        </select>
        <br />
        <br />
        <button type="submit">
            add restaurant</button>
        </form>
    </div>

At the end we need implement basic restaurants model functions

 function RestaurantViewModel() {
            var self = this;
            self.restaurants = ko.observableArray([{ name: 'Restaurant 1', place: 'London' }, { name: 'Restaurant 2', place: 'Tokyo' }, { name: 'Restaurant 3', place: 'Paris'}]);

            self.removeRestaurant = function (restaurant) {
                self.restaurants.remove(restaurant)
            }

            self.addRestaurant = function (restaurantForm) {
                //basic validation
                if (restaurantForm.name.value.trim().length == 0) {
                    alert('Type the restaurant name!'); return;
                }

                var restaurant = { name: restaurantForm.name.value, place: restaurantForm.place.value };
                self.restaurants.push(restaurant);

                restaurantForm.name.value = '';  //clear form
            }
        }

I have included sample project to be downloaded below. Please use it to check how binding is working when adding, editing and deleting objects on the page.
knockoutjs

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Programmatically creating scheduled task in Windows Task Scheduler

If you are creating windows forms application that has to perform some tasks on the regular basis the good solution is to use built-in Windows Task Scheduler. It is very stable and reliable as long as the user’s computer is working.

In this article I will show you how to create scheduled task by using Microsoft.Win32.TaskScheduler.dll (managed wrapper dll).

Let’s create our Scheduler class with the function to set up win scheduler. In our example we will run either an external file or test application depending on the parameters passed in.

After creating new instance of the TaskService, we need to set up some basic parameters (names and description to be shown in windows scheduler UI). Next, we need to create trigger, in our case we will use weekly trigger as the task will be performed every day of the week that user will configure.

After that we have to set up execution path and additional startup arguments if required. This is useful to detect from the target application that it has been run automatically and based on that to apply different layout, colors, buttons etc.

 public class Scheduler
 {
    public static string TaskName = "MyTask";

    public static void SetTask(string filePath, DateTime startDate, DaysOfTheWeek daysOfWeek, bool enabled)
    {
        var ts = new TaskService();
        var td = ts.NewTask();
        td.RegistrationInfo.Author = "My company";
        td.RegistrationInfo.Description = "Runs test application";
        td.Triggers.Add(new WeeklyTrigger { StartBoundary = startDate, DaysOfWeek = daysOfWeek, Enabled = enabled });

        //run this application or setup path to the file
        var action = new ExecAction(Assembly.GetExecutingAssembly().Location, null, null);
        if (filePath != string.Empty && File.Exists(filePath))
        {
            action = new ExecAction(filePath);
        }
        action.Arguments = "/Scheduler";
        td.Actions.Add(action);

        ts.RootFolder.RegisterTaskDefinition(TaskName, td);
    }

Next let’s create other basic functions to get and delete current task. This is quite straightforward.

 public static void DeleteTask()
 {
    var ts = new TaskService();
    var task = ts.RootFolder.GetTasks().Where(a => a.Name.ToLower() == TaskName.ToLower()).FirstOrDefault();
    if (task != null)
    {
        ts.RootFolder.DeleteTask(TaskName);
    }
 }

 public static Task GetTask()
 {
    var ts = new TaskService();
    var task = ts.RootFolder.GetTasks().Where(a => a.Name.ToLower() == TaskName.ToLower()).FirstOrDefault();

    return task;
 }

Now, lets create function to display next scheduled task run date. It is quite useful to notify the user about the next planned execution. In our implementation we will use NextRunTime property of the task we created. We also have to check if the task is enabled.

 public static string GetNextScheduleTaskDate()
 {
    try
    {
        var task = Scheduler.GetTask();
        if (task != null)
        {
            var trigger = task.Definition.Triggers.FirstOrDefault();
            if (trigger != null)
            {
                if (trigger.TriggerType == TaskTriggerType.Weekly)
                {
                    if (trigger.Enabled)
                    {
                        var weeklyTrigger = (WeeklyTrigger)trigger;
                        
                        return task.NextRunTime.ToString("yyyy MMM. dd dddd 'at ' HH:mm");
                    }
                }
            }
        }
    }
    catch (Exception)
    { }

    return "no scheduled date!";
 }

Within the UI, we have to create basic helper methods in order to support interface values. In our case we will use checkboxes to set and enable task. In our example, the user will be able to set the task in any day of the week, disable it or delete it permanently. We will also display the next run time information.

 void RefreshNextRunInfo()
 {
    lblNextRun.Text = Scheduler.GetNextScheduleTaskDate();
 }

 //Load selected days from checkboxes
 DaysOfTheWeek GetTaskDays()
 {
    var daysOfWeek = DaysOfTheWeek.AllDays;
    daysOfWeek &= ~DaysOfTheWeek.AllDays;

    if (chkbMonday.Checked) { daysOfWeek = daysOfWeek | DaysOfTheWeek.Monday; }
    if (chkbSaturday.Checked) { daysOfWeek = daysOfWeek | DaysOfTheWeek.Saturday; }
    if (chkbSunday.Checked) { daysOfWeek = daysOfWeek | DaysOfTheWeek.Sunday; }
    if (chkbThursday.Checked) { daysOfWeek = daysOfWeek | DaysOfTheWeek.Thursday; }
    if (chkbTuestday.Checked) { daysOfWeek = daysOfWeek | DaysOfTheWeek.Tuesday; }
    if (chkbWednesday.Checked) { daysOfWeek = daysOfWeek | DaysOfTheWeek.Wednesday; }
    if (chkbFriday.Checked) { daysOfWeek = daysOfWeek | DaysOfTheWeek.Friday; }

    return daysOfWeek;
 }

 void SetTaskForm(bool enabled)
 {
    timePicker.Enabled = enabled;
    chkbFriday.Enabled = enabled;
    chkbMonday.Enabled = enabled;
    chkbSaturday.Enabled = enabled;
    chkbSunday.Enabled = enabled;
    chkbThursday.Enabled = enabled;
    chkbTuestday.Enabled = enabled;
    chkbWednesday.Enabled = enabled;
 }

 //reload current task state (if exists)
 public void RefreshSchedulerSettings()
 {
    //set initial time to 5 minutes ahead for testing
    timePicker.Value = DateTime.Now.AddMinutes(5);

    try
    {
        SetTaskForm(false);
        chkbFriday.Checked = false;
        chkbMonday.Checked = false;
        chkbSaturday.Checked = false;
        chkbSunday.Checked = false;
        chkbThursday.Checked = false;
        chkbTuestday.Checked = false;
        chkbWednesday.Checked = false;
        chkbScheduler.Checked = false;

        var task = Scheduler.GetTask();
        if (task != null)
        {
            var trigger = task.Definition.Triggers.FirstOrDefault();
            if (trigger != null)
            {
                if (trigger.TriggerType == TaskTriggerType.Weekly)
                {
                    if (trigger.Enabled) { SetTaskForm(true); }

                    chkbScheduler.Checked = trigger.Enabled;
                    var weeklyTrigger = (WeeklyTrigger)trigger;
                    timePicker.Value = trigger.StartBoundary;

                    if (weeklyTrigger.DaysOfWeek.HasFlag(DaysOfTheWeek.Friday)) { chkbFriday.Checked = true; }
                    if (weeklyTrigger.DaysOfWeek.HasFlag(DaysOfTheWeek.Monday)) { chkbMonday.Checked = true; }
                    if (weeklyTrigger.DaysOfWeek.HasFlag(DaysOfTheWeek.Saturday)) { chkbSaturday.Checked = true; }
                    if (weeklyTrigger.DaysOfWeek.HasFlag(DaysOfTheWeek.Sunday)) { chkbSunday.Checked = true; }
                    if (weeklyTrigger.DaysOfWeek.HasFlag(DaysOfTheWeek.Thursday)) { chkbThursday.Checked = true; }
                    if (weeklyTrigger.DaysOfWeek.HasFlag(DaysOfTheWeek.Tuesday)) { chkbTuestday.Checked = true; }
                    if (weeklyTrigger.DaysOfWeek.HasFlag(DaysOfTheWeek.Wednesday)) { chkbWednesday.Checked = true; }
                }
            }
        }
    }
    catch (Exception)
    { }
 } 

When saving the task we will use following method that validates and gathers user input information. Your implementation of course will depend on your requirements.

 private void btnSave_Click(object sender, EventArgs e)
 {
    var filePath = string.Empty;//set path to the exe file or leave it empty to run this app for test
    var daysOfWeek = GetTaskDays();

    if (chkbScheduler.Checked && daysOfWeek == 0)
    {
        MessageBox.Show(this, "Select at least one day or turn off scheduler!", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
        return;
    }

    try
    {
        if (daysOfWeek != 0)
        {
            var startDate = DateTime.Now.Date.AddHours(timePicker.Value.Hour).AddMinutes(timePicker.Value.Minute);
            Scheduler.SetTask(filePath, timePicker.Value, daysOfWeek, chkbScheduler.Checked);
        }
        else
        {
            Scheduler.DeleteTask();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(this, "An error occured " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    }
    RefreshNextRunInfo();
 }

I have included fully working example below for your tests.

TaskScheduler

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Using automation when executing SQL scripts stored in separate files

When working on a big projects, sometimes there is a need to update database from the sql files scattered around multiple files or folders especially when using source control for maintaining database changes. Also when using auto builds via CI server this functionality seems to be useful.

In this article I will show you how to automate the process of updating multiple database sql files so you can do it in one go – like that:

  //this is command line executed by TeamCity build (change params with your build configuration values)
 c:\\UpdateScripts.exe "\scripts\Procedures" %Server% %Database% %ToBeReplacedSelectUser% %DatabaseSelectUser% "exec dbo.proc_GrantExecutePermissionsOnProcedures '%DatabaseUser%'"

teamCitySqlCmd

Lets create C# console application first. Once that is done we can implement basic validation rules for parameters being passed in. In our example we will support 6 parameters: Server, Database, StringToFind, StringToReplace, DatabaseUser, FinalQuery (we will use windows integrated database connection).

    if (args.Length == 0)
    {
        Console.WriteLine("No arguments passed!");
        Console.WriteLine(Environment.NewLine);
        Console.WriteLine(getHelp());
        Console.ReadLine();
        return 1;
    }

    if (args.Length < 3)
    {
        if (!silent)
        {
            Console.WriteLine("Not all required arguments passed!");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine(getHelp());
            Console.ReadLine();
        }
        return 1;
    } 
    ///////////////
    var scriptPath = args[0];
    var server = args[1];
    var database = args[2];
    var findString = args[3];
    var replaceString = args[4];
    var endQuery = args.Length >= 5 ? args[5] : string.Empty;
    /////////////

    if (!Directory.Exists(scriptPath) || Directory.GetFiles(scriptPath).Length == 0)
    {
        if (!silent)
        {
            Console.WriteLine("Directory does not exist or is empty!");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine(getHelp());
            Console.ReadLine();
        }
        return 1;
    }

The code below will get all .sql files from the directory and execute containing queries. If an file has “GO” statements inside then it needs to be executed in one block to avoid errors. Normally when executing such a scripts, Sql Management Studio cares about that. In our case we simply need to split content by “GO” blocks.

Console.WriteLine("params to find: " + findString);
Console.WriteLine("replace string: " + replaceString);

using (var con = new SqlConnection("Server=" + server + ";Database=" + database + ";Trusted_Connection=True;"))
{
    con.Open();

    var files = Directory.GetFiles(scriptPath).Where(f => f.ToLower().EndsWith(".sql"));

    foreach (var filePath in files)
    {
        #region execute query from file
        var query = File.ReadAllText(filePath);

        if (findString.Length > 0)
        {
            query = query.Replace(findString, replaceString);
        }
        Console.WriteLine("Executing: " + Path.GetFileName(filePath));

        query = query.Replace("go\r\n", "GO\r\n");
        query = query.Replace("go\t", "GO\t");
        query = query.Replace("\ngo", "\nGO");

        //divide into GO groups to avoid errors
        var itemQuery = query.Split(new string[] { "GO\r\n", "GO\t", "\nGO" }, StringSplitOptions.None);

        foreach (var queryItem in itemQuery)
        {
            if (queryItem.Trim().Length < 3) { continue; }

            using (var cmd = new SqlCommand(queryItem, con))
            {
                try
                {
                    cmd.ExecuteNonQuery();
                }
                catch (Exception ex)
                {
                    if (!silent)
                    {
                        Console.WriteLine("Error: " + ex.Message);
                    }
                    exitResult = 1;
                }
            }
        }
        #endregion
    }

At the end we can execute final sql statement. It is useful when for example there is a need to assign database permissions after the update is performed.

 //////////////////////execute final query (e.g permission update)
if (endQuery.Trim().Length > 3)
{
    using (var cmd = new SqlCommand(endQuery, con))
    {
        try
        {
            Console.WriteLine("Executing final query: " + endQuery);
            cmd.ExecuteNonQuery();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
            return 1;
        }
    }
}

The Console.WriteLine output will also be picked up by the TeamCity build log, so you can see all update information when needed. I have attached solution files, feel free to adjust it to your needs 🙂
UpdateDBScripts

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

Creating custom sound wave analysis control

The very common requirement when working with the sound files is to display and analyze the sound wave within the form element. The best way of doing that is to create custom sound wave analysis control that we can reuse throughout the multiple projects.

In this article I will show you how to create control that draws the wave on the form. We will use Open source nAudio dll to load sound files. Then we will perform custom drawings based on the extracted sound samples.

soundwave_analysis

Let’s create CustomWaveViewer class that inherits from UserControl. After that we have to define basic variables, the handlers and the rendering thread. The rendering thread will be used when user subscribe to our event handler wanting to take control over the rendering speed. Otherwise the control will render immediately and no additional thread is needed.

 public class CustomWaveViewer : UserControl
 {
   public delegate void OnLineDrawHandler(Point point1, Point point2);
   public event OnLineDrawHandler OnLineDrawEvent;
   private WaveStream waveStream;
   private Thread RenderThread;
    .....
}

When initiating the control we will set double buffer and basic variables. When user will call InitControl, control will be recalculated to fit to our window size.

  public CustomWaveViewer()
 {
    // This call is required by the Windows.Forms Form Designer.
    InitializeComponent();

    //use double buffer to avoid flickering
    this.DoubleBuffered = true;

    this.PenColor = Color.Lime;
    this.PenWidth = 1;
 }

 public void InitControl()
 {
    if (waveStream == null) return;

    var samples = (int)(waveStream.Length / bytesPerSample);
    startPosition = 0;
    SamplesPerPixel = samples / this.Width;
}

After the sound file is loaded we need to calculate bytesPerSample variable that depends on BitsPerSample value and the number of channels.

 public WaveStream WaveStream
 {
    get
    {
        return waveStream;
    }
    set
    {
        waveStream = value;
        if (waveStream != null)
        {
            bytesPerSample = (waveStream.WaveFormat.BitsPerSample / 8) * waveStream.WaveFormat.Channels;
        }
        this.Invalidate();
    }
}

Before we will render the lines we want to create grid lines by using following method:

 private void DrawImageGrid(System.Drawing.Graphics gfx)
 {
    int numOfCells = 30; int cellSize = (int)(gfx.ClipBounds.Height / 4);

    for (int y = 0; y < numOfCells; ++y)
    {
        gfx.DrawLine(new System.Drawing.Pen(System.Drawing.Color.Gray, 1), 0, y * cellSize, numOfCells * cellSize, y * cellSize);
    }

    for (int x = 0; x < numOfCells; ++x)
    {
        gfx.DrawLine(new System.Drawing.Pen(System.Drawing.Color.Gray, 1), x * cellSize, 0, x * cellSize, numOfCells * cellSize);
    }
}

The main method that renders the lines is the OnPaint method of our custom control. In that method we either draw the lines immediately or pass it to the other thread that user can control from the UI. See the in-line comments:

 protected override void OnPaint(PaintEventArgs e)
 {
    try
    {
        //draw grid before drawing the wave
        DrawImageGrid(e.Graphics);

        if (waveStream != null)
        {
            int bytesRead;
            var waveData = new byte[samplesPerPixel * bytesPerSample];
            waveStream.Position = startPosition + (e.ClipRectangle.Left * bytesPerSample * samplesPerPixel);
            ControlPoints.Clear();// clear points 

            using (var linePen = new Pen(PenColor, PenWidth))
            {
                for (var x = e.ClipRectangle.X; x < e.ClipRectangle.Right; x += 1)
                {
                    short low = 0;
                    short high = 0;

                    bytesRead = waveStream.Read(waveData, 0, samplesPerPixel * bytesPerSample);

                    if (bytesRead == 0) { break; }

                    for (var n = 0; n < bytesRead; n += 2)
                    {
                        var sample = BitConverter.ToInt16(waveData, n);
                        if (sample < low) { low = sample; }
                        if (sample > high) { high = sample; }
                    }

                    //calculate min and max values for the current line
                    var lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
                    var highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);

                    var point1 = new Point(x, (int)(this.Height * lowPercent));
                    var point2 = new Point(x, (int)(this.Height * highPercent));

                    //if event is not hoocked in, then render wave instantly
                    if (OnLineDrawEvent == null)
                    {
                        e.Graphics.DrawLine(linePen, point1, point2);
                    }
                    else
                    {
                        //save points to be used in the rendering thread
                        ControlPoints.Add(point1, point2);
                    }
                }
            }

            //abort current thead if sound has been reloaded
            if (RenderThread != null && RenderThread.IsAlive)
            {
                RenderThread.Abort();
            }

            //start rendering thread
            RenderThread = new Thread(TriggerDrawing);
            RenderThread.IsBackground = true;
            RenderThread.Start();
        }
    }
    catch (Exception)
    {
    }

    base.OnPaint(e);
}

When we pass the rendering to other thread, we simply need to trigger OnLineDrawEvent event for each line stored in our buffer (ControlPoints variable). The UI will then receive the event with the line data.

 void TriggerDrawing()
 {
    //check if the event is attached
    if (OnLineDrawEvent != null)
    {
        foreach (var pointSet in ControlPoints)
        {
            OnLineDrawEvent(pointSet.Key, pointSet.Value);//trigger event and pass the points to the UI
            Thread.Sleep(RenderDelay); //set custom delay
        }
    }
}

Our main UI methods will look as follows. After dropping our custom control on the form, we need to configure it by passing sound wave file path and attach rendering method. The RenderDelay variable will also be used by slider to dynamically control rendering speed.

 private void Form1_Load(object sender, EventArgs e)
 {
    comboBox1.SelectedIndex = 0;//select first sound

    //Attach draw event otherwise the control will render instantly
    customWaveViewer1.OnLineDrawEvent += new CustomWaveViewer.OnLineDrawHandler(customWaveViewer1_OnLineDrawEvent);

    //set initial speed - it can be configured using UI slider
    customWaveViewer1.RenderDelay = 20;

    LoadWaive();//init control
 }

 void LoadWaive()
 {
    var path = "../../sounds/" + comboBox1.Text.Last<char>().ToString() + ".wav";

    customWaveViewer1.WaveStream = new WaveFileReader(path);
    customWaveViewer1.InitControl();
 }

 //Draw the lines when the points arrive from the background thread event
 void customWaveViewer1_OnLineDrawEvent(Point point1, Point point2)
 {
    using (var gr = customWaveViewer1.CreateGraphics())
    {
        using (var linePen = new Pen(Color.Lime, 1))
        {
            gr.DrawLine(linePen, point1, point2);
        }
    }
 }

I have attached working example below for your tests. You may also use other nAudio methods to play the sound file while it’s being rendered.

SoundAnalysis

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

C# commonly used coding standards

In this article I will present you with some most commonly used coding standards that I have come across while working in multiple companies.

1. Naming Conversions

1.1 Use Pascal Casing for class and method names and camel Casing for local variables

Pascal Casing:

public class MyClass
{
      public void GetData()
     {
     }
}

Camel Casing

public class MyClass
{
      public void GetData(string productName)
     { 
          var myProduct = GetProductFromDatabase(productName);
     }
}

1.2 Do not use underscores for member names eg.

//correct

public string myVariable;
public int myProduct;

//incorrect

public string my_variable;
public int _ myProduct;

1.3 Do not use shortcuts eg.

//correct
public string databaseConnectionString;

//incorrect
public string dbConnStr;

1.4 Give the function names appropriate to the returning values eg.

//correct
public  string GetProductReferenceNumber()
{
}

//incorrect
public  string GetObjectString()
{
}

1.5 Use predefined type names instead of system names eg.

//correct
int ProductID;
string ProductName;

//incorrect
Int32 ProductID;
String ProductName;

1.6 Use the var name for variable declaration if the type can be easily identified from the right hand side of the equation eg.

//correct
var name = "myname";

//incorrect
var myObject = GetBasicObject();

1.7 Use noun names for classes

//correct
public class Product
{
}

//incorrect
public class UserProduct
{
}

1.8 Use „I” prefix for interface names eg.

public interface IProductCollection

1.9 Declare private variables on the top of the classes starting with statics on the top

public class Product
{
    static string productName;
    static int productCount;

    int currectCount;
    string productLocationName;

    public void Calculate()
    {
    }
}

1.10 Use singular names for enums except bit field enums eg.

//correct
enum ProductType
{
    ProductTypeOne, ProductTypeTwo
}

//incorrect
enum ProductTypes
{
    ProductTypeOne, ProductTypeTwo
}

//correct
enum ProductTypes
{
    ProductTypeOne = 1, 
    ProductTypeTwo =2
}

2. General rules

2.1 Use && and || operands instead of single & Or |. This will avoid unnecessary comparisons eg.

//correct
If(product != null && (product.Id > 0 || product.Name.length > 0))
{
}

//incorrect
If(product != null & (product.Id > 0 | product.Name.length > 0))
{
}

2.2 Always use (vertically aligned) curly brackets eg.

//correct
foreach(var item In myCollection)
{
      var result = item.Result;
}

//incorrect
foreach(var item In myCollection)
        var result = item.Result;

2.3 Use „using” statements for objects that implement IDisposable interface eg.

//correct
using(var connection = new SqlConnection())
{
     using( var reader = command.ExecuteReader())
     {
     }
}

//incorrect
var connection = new SqlConnection();
var reader = command.ExecuteReader();

reader.close();
connection.close();

2.4 Use lambda expressions for events that you do not need have reference to at later stage in you code eg.

//correct 
public MyForm()
{
   this.Click += (s, e) =>   {
       MessageBox.Show(((ClickEventArgs)e).Point.ToString());
  };
}
//not advisable 
public MyForm()
{
      this.Click += new EventHandler(MyFunction_Click);
}

void Form1_Click(object sender, ClickEventArgs e)  
{     
     MessageBox.Show(((ClickEventArgs)e).Point.ToString());
 }

2.5 Use object initializers for creating object eg.

//correct
var product = new Product()
{
   ProductID = 1,
   ProductName = “Test”,
   CreateDate = DateTime.Now
}

//incorrect
 var product = new Product();
 product. ProductID = 1,
 product .ProductName = “Test”,
 product .CreateDate = DateTime.Now

2.6 Each function should only perform one task eg.

//correct
public int CalculatePriceWithVAT(double initPrice, double vatRate)
{
      return initPrice = initPrice * (1- vatRate);
}
//incorrect
public int CalculatePrice ()
{
      var initPrice = GetInitPrice();
      var vatRate = GetVatFromDataBase();

      return initPrice = initPrice * (1- vatRate);
}

2.7 Separate parts of the code into logical groups eg.

//correct
int CalculateStock()
{
     var fakeValue = 12;
      fakeValue += 12;

      var fakeProduct = new Product();
      var result = fakeProduct.Count + fakeValue;

      return result;
}
//avoid
int CalculateStock()
{
     var fakeValue = 12;
      fakeValue += 12;
      var fakeProduct = new Product();
      var result = fakeProduct.Count + fakeValue;
      return result;
}

2.8 In LINQ use following to check bool values eg:

//correct
If(products.Any())
{
}

//incorrect
If(products.Count() > 0 )
{
}
1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5.00 out of 5)
Loading...Loading...

MVC Custom View Editor Templates

When creating MVC3 razor website it is often necessary to apply custom templates on classes or properties within the objects to be displayed seamlessly throughout your application. There is number of ways to apply custom templates, one of them is to create folders in ~/Shared/DisplayTemplates/, ~/Shared/EditorTemplates/ and create partial views based on model objects we want to style. Lets start with creating basic class structure that we will work on:

custom-templates

  public partial class Client
    {
        public int ClientId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public Address HomeAddress { get; set; }

        [UIHint("bool")]
        public bool IsApproved { get; set; }

        [UIHint("Enum")]
        public Role Role { get; set; }
    }

    public enum Role
    {
        Admin, PM, Employee
    }

    public partial class Address
    {
        public string Street { get; set; }
        public string City { get; set; }
        public string Country { get; set; }
    }

As you can see there are two properties marked with the UIHint attribute. This is one of the ways to map properties with the templates.

When displaying form we can also specify template name if we want to apply template only for selected pages.

  <div class="editor-field">
        @Html.EditorFor(model => model.IsApproved)
        @Html.ValidationMessageFor(model => model.IsApproved)
    </div>

    <div class="editor-field">
        @Html.EditorFor(model => model.Role)
        @Html.ValidationMessageFor(model => model.Role)
    </div>

This is similar when creating display templates. We can also mix the ways of assigning templates by choosing the properties attributes and template names if required.

 <div class="display-field">
        @Html.DisplayFor(model => model.IsApproved)
    </div>
    <div class="display-label">
        Role</div>
    <div class="display-field">
        @Html.DisplayFor(model => model.Role, "MyRole")
    </div>

Our editor template implementation is quite simple. For each enum value in our Role class we create list item to be shown in drop down list. We also need to ensure that we give it a proper name Role.cshtml in order for the view engine to be able to correctly identify our template.

 @model Models.classes.Role
           
   <select id="Role" name="Role">
    @foreach (Models.classes.Role value in Enum.GetValues(typeof(Models.classes.Role)))
    {
        <option value="@value" @(Model == value ? "selected=\"selected\"" : "")>@value
        </option>
    }
  </select>

Display template contains the code below. We simply mark in red the current Role value. Of course it’s only a quick sample.

 @model Models.classes.Role
 @foreach (Models.classes.Role value in Enum.GetValues(typeof(Models.classes.Role)))
 {
    if (Model == value)
    {
      <p><b style="color:Red;">@value.ToString()</b></p>;
    }
    else
    {
       <p>@value.ToString()</p>
    }
 }

If we want to override the templates located in the Shared folder, we can create folder in the view (~/Views//EditorTemplates) containing different implementation. This gives us a little bit more flexibility if we have specific requirements for an single view.

We can also create generic templates to override default rendering of build-in types like bool? We apply it also by giving the attribute to the property of our object. Following implementation displays in bold one of the 3 possible object states.

    @model bool?

    @if (ViewData.ModelMetadata.IsNullableValueType && Model == null)
    {
        @:True False <b>Not Set</b>
    }
    else if (Model.Value)
    {
        @:<b>True</b> False Not Set
    }
    else
    {
        @:True <b>False</b> Not Set
    }

MVC framework gives us a chance to override and customize pretty much of everything. We can also create our own View Engine and change the way of model binding etc.

I have included project sample below for you tests.
MVC-Custom-display-templates

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...

OpenXml Power Point templates processing

Handling Power Point templates is quite similar to handling Words templates as they both are based on OpenXML standard (see my previous article to learn how to process Word templates). In this article I will show you how to replace Power Point template with text and images using C# based application.

After creating new project we need to add two main references: DocumentFormat.OpenXml from DocumentFormat.OpenXml.dll and WindowsBase (.Net reference). Next, we need to prepare template with placeholders that our application will replace. For texts we will insert placeholder values in the the unique format that we can find parsing the document e.g. [#Paragraph1#].

For the images we need to insert image placeholders (other images) and set their names to match our parameters (e.g. myPicture1). This names needs to be provided in the parameter objects so we can find the placeholders by image name when parsing document.

powerpoint-templates

The next step is to create parameters structure that we will use when processing the document. You may want to set your own parameters depending on your project requirements.

 public class PowerPointParameter
 {
    public string Name { get; set; }
    public string Text { get; set; }
    public FileInfo Image { get; set; }
 }

We will use them as follows when initiating the objects

   var templ = new PowerPointTemplate();
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#Paragraph1#]", Text = "Slide 1" });
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#Paragraph2#]", Text = "Slide 2" });
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#List1(string[])#]", Text = "test1 \n test 2 \n test3 \n test 2" });
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "[#List2(string[])#]", Text = "test1 \n test 2 \n test3 \n test 2" });

    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "1", Image = new FileInfo(GetRootPath() + @"\Images\1.jpg") });
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "2", Image = new FileInfo(GetRootPath() + @"\Images\2.jpg") });
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "3", Image = new FileInfo(GetRootPath() + @"\Images\3.jpg") });
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "4", Image = new FileInfo(GetRootPath() + @"\Images\4.jpg") });
    templ.PowerPointParameters.Add(new PowerPointParameter() { Name = "5", Image = new FileInfo(GetRootPath() + @"\Images\5.jpg") });

    var templatePath = GetRootPath() + @"\Templates\Template.pptx";
    var outputPath = GetRootPath() + @"\Output\Document.pptx";

    templ.ParseTemplate(templatePath, outputPath);

Having done that we can create our main function that parses the template and fills our placeholders with texts and images. We may also want to delete an slide from presentation, we can do that by getting the slideID from the SlideIdList property and use RemoveChild function. Please see the inline comments within the function below.

 public void ParseTemplate(string templatePath, string templateOutputPath)
 {
    using (var templateFile = File.Open(templatePath, FileMode.Open, FileAccess.Read)) //read our template
    {
        using (var stream = new MemoryStream())
        {
            templateFile.CopyTo(stream); //copy template

            using (var presentationDocument = PresentationDocument.Open(stream, true)) //open presentation document
            {
                // Get the presentation part from the presentation document.
                var presentationPart = presentationDocument.PresentationPart;

                // Get the presentation from the presentation part.
                var presentation = presentationPart.Presentation;

                var slideList = new List<SlidePart>();

                //get available slide list
                foreach (SlideId slideID in presentation.SlideIdList)
                {
                    var slide = (SlidePart)presentationPart.GetPartById(slideID.RelationshipId);
                    slideList.Add(slide);
                    SlideDictionary.Add(slide, slideID);//add to dictionary to be used when needed
                }

                //loop all slides and replace images and texts
                foreach (var slide in slideList)
                {
                    ReplaceImages(presentationDocument, slide); //replace images by name

                    var paragraphs = slide.Slide.Descendants<Paragraph>().ToList(); //get all paragraphs in the slide

                    foreach (var paragraph in paragraphs)
                    {
                        ReplaceText(paragraph); //replace text by placeholder name
                    }
                }

                var slideCount = presentation.SlideIdList.ToList().Count; //count slides
                DeleteSlide(presentation, slideList[slideCount - 1]); //delete last slide

                presentation.Save(); //save document changes we've made
            }
            stream.Seek(0, SeekOrigin.Begin);//scroll to stream start point

            //save output file
            using (var fileStream = File.Create(templateOutputPath))
            {
                stream.CopyTo(fileStream);
            }
        }
    }
}

Function that replaces the images. It gets all Blip objects from the slide and changes it’s embed ID that points to the image.

Similar to Word templates, we can give our own styles and transformation to the image template and it will be preserved and applied to the new image 🙂

When replacing the images we search for all image parts in a slide and then check it’s names using NonVisualDrawingProperties of the Picture elements. Please see the inline comments.

 void ReplaceImages(PresentationDocument presentationDocument, SlidePart slidePart)
 {
    // get all images in the slide
    var imagesToReplace = slidePart.Slide.Descendants<Blip>().ToList();

    if (imagesToReplace.Any())
    {
        var index = 0;//image index within the slide

        //find all image names in the slide
        var slidePartImageNames = slidePart.Slide.Descendants<DocumentFormat.OpenXml.Presentation.Picture>()
                                .Where(a => a.NonVisualPictureProperties.NonVisualDrawingProperties.Name.HasValue)
                                .Select(a => a.NonVisualPictureProperties.NonVisualDrawingProperties.Name.Value).Distinct().ToList();

        //check all images in the slide and replace them if it matches our parameter
        foreach (var imagePlaceHolder in slidePartImageNames)
        {
            //check if we have image parameter that matches slide part image
            foreach (var param in PowerPointParameters)
            {
                //replace it if found by image name
                if (param.Image != null && param.Name.ToLower() == imagePlaceHolder.ToLower())
                {
                    var imagePart = slidePart.AddImagePart(ImagePartType.Jpeg); //add image to document

                    using (FileStream imgStream = new FileStream(param.Image.FullName, FileMode.Open))
                    {
                        imagePart.FeedData(imgStream); //feed it with data
                    }

                    var relID = slidePart.GetIdOfPart(imagePart); // get relationship ID

                    imagesToReplace.Skip(index).First().Embed = relID; //assign new relID, skip if this is another image in one slide part

                    //to change picture size dynamically you can use this code
                    //int width = 150; int height = 100;
                    //int imageWidthEMU = (int)Math.Round((decimal)width * 9525);
                    //int imageHeightEMU = (int)Math.Round((decimal)height * 9525);

                    //var picture = (DocumentFormat.OpenXml.Presentation.Picture)imagesToReplace.Skip(index).First().Parent.Parent;
                    //picture.ShapeProperties.Transform2D.Extents.Cx = imageWidthEMU;
                    //picture.ShapeProperties.Transform2D.Extents.Cy = imageHeightEMU;
                    ///////
                }
            }
            index += 1;
        }
    }
}

When replacing the texts we check if paragraph contains the text that matches our parameter. If yes, then we check if to include one or multiple lines of text.
Next we create new parameter by copying the old parameter’s OuterXML (this preserves the styles). We also need to replace text that is stored in our parameter.

 void ReplaceText(Paragraph paragraph)
 {
    var parent = paragraph.Parent; //get parent element - to be used when removing placeholder
    var dataParam = new PowerPointParameter();

    if (ContainsParam(paragraph, ref dataParam)) //check if paragraph is on our parameter list
    {
        //insert text list
        if (dataParam.Name.Contains("string[]")) //check if param is a list
        {
            var arrayText = dataParam.Text.Split(Environment.NewLine.ToCharArray()); //in our case we split it into lines

            if (arrayText is IEnumerable) //enumerate if we can
            {
                foreach (var itemData in arrayText)
                {
                    Paragraph bullet = CloneParaGraphWithStyles(paragraph, dataParam.Name, itemData);// create new param - preserve styles
                    parent.InsertBefore(bullet, paragraph); //insert new element
                }
            }
            paragraph.Remove();//delete placeholder
        }
        else
        {
            //insert text line
            var param = CloneParaGraphWithStyles(paragraph, dataParam.Name, dataParam.Text); // create new param - preserve styles
            parent.InsertBefore(param, paragraph);//insert new element

            paragraph.Remove();//delete placeholder
        }
    }
}

Creating the new paragraph object preserving the styles

 public static Paragraph CloneParaGraphWithStyles(Paragraph sourceParagraph, string paramKey, string text)
 {
    var xmlSource = sourceParagraph.OuterXml;

    xmlSource = xmlSource.Replace(paramKey.Trim(), text.Trim());

    return new Paragraph(xmlSource);
}

Please note that when replacing images, the image placeholder names must be found in the document. Images that don’t match the parameter name, wont be replaced.

Having done that we can finally test our application. I have included complete working application for your tests.

PowerPointTemplates

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...Loading...