déc. 03

Summary

The scenario

  • You have created a VSTO project, you have so a document (let's say ".xlsx") associated with it
  • For any reason, you don't want the VSTO project to be the startup project
  • You want the startup project to be a Console application
  • You want to open the VSTO document from command line
  • You want, via the Console Application, to manipulate any VSTO object (typically Globals.ThisWorkbook, ...)

Launching a VSTO document in command line

Let's first see how we can launch the document :

  • Let's create a Console Application and let's add some references
    • System.Windows.Forms.dll
    • Microsoft.Office.Interop.Excel.dll
    • Microsoft.Office.Tools.Excel.v9.0.dll
    • Microsoft.Office.Tools.v9.0.dll
    • Microsoft.VisualStudio.Tools.Applications.Runtime.v9.0.dll
  • Let's code our Main method
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using Microsoft.Office.Tools.Excel.Extensions;
 
using IExcel = Microsoft.Office.Interop.Excel;
using TExcel = Microsoft.Office.Tools.Excel;
 
namespace ClientApplication
{
   class Program
   {
      static int Main(string[] args)
      {
         /* To avoid any problem (exception of type "Invalid format") when manipulating the 
          * workbook, we'll specify here the culture en-US to avoid forcing the client to 
          * have a language pack for excel
          */
         Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
 
         //Get the application and set it visible (ie, seeing the excel front end)
         IExcel.Application application = new IExcel.Application();
         application.Visible = true;
 
         //Let's create the path to the file and then : open it
         string basePath = @"D:\Projects\PDA - Blog\Blog Research\VSTO - WCF\ServerApplication";
         string fileName = "ServerApplication.xlsx";
         IExcel.Workbook book = application.Workbooks.Open(Path.Combine(basePath, fileName),
            Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
            Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
            Type.Missing, Type.Missing, Type.Missing, Type.Missing);
 
         /* Let's now use the .NET 3.5 SP1 functionality to convert an interop
          * object to a VSTO object
          * As it is an extension method, we could also use
          * TExcel.Workbook workbook = book.GetVstoObject();
          */
         TExcel.Workbook workbook = WorkbookExtensions.GetVstoObject(book);
 
         //Let's now check that the conversion did work
         Debug.Assert(workbook != null, "The conversion to VSTO did not work");
         //It will fail !
 
         return 0;
      }
   }
}

Note also that when you work with assertion, you must update the config file as follows :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.diagnostics>
      <assert assertuienabled="true" 
              logfilename="D:\\Projects\\PDA - Blog\\Blog Research\\VSTO - WCF\assert.log" />
   </system.diagnostics>
</configuration>

As a consequence, if the assertion fails, you will receive a prompt in Visual Studio giving the exception, and furthermore, you will get the error logged in the text file you have specified.

Why does this fail ?

Yes the conversion fails. Meaning that it is impossible to convert an interop object to a VSTO object when opening from command line. The main reason is that the VSTO objects are attached to a host (ie the excel application), host that is created at design time and that cannot be created at runtime.

In other words, we just cannot use the VSTO objects outside of the excel application. Does that mean that we cannot fulfill the scenario?

There are some possibilities to workaround this, and I will present one of them below. If we cannot use the VSTO objects outside of the excel application, we can (of course) manipulate them when we are inside of the VSTO project. So one solution would be to let the VSTO project working.

How can we achieve that ? We can imagine a client / server application, using .NET remoting. The VSTO project (ie the server) can launch a remoting server and the console application (ie the client) can connect to it to send the commands to be executed on the VSTO objects. Let's see that working.

.NET remoting inside of an excel application


Let's create a communication object

We'll want to exchange some information from the client to the server. To do that, we'll need to work with a communication structure that can be sent over the pipe. Not a lot of choice for that : we need a MarshalByRefObject. This structure will be used by both the client application and the server application, and this object will be responsible of manipulating the real VSTO objects. We'll so add it to the server application.

This mean that your client application will need to reference the server application. As we cannot reference the project directly, we'll reference the DLL that is generated.

  • Add a reference (in the client and server applications) to "System.Runtime.Remoting"
  • Add a reference (in the client application) to the server application
  • Be careful ! Be sure, when testing your client app, to build your server app before !

We are now ready to create our communication object.

public class RemoteApplication : MarshalByRefObject
{
   private static RemoteApplication application = new RemoteApplication();
 
   /// <summary>
   /// The server application will use this singleton to set this type public 
   /// for remoting
   /// </summary>
   internal static RemoteApplication Instance
   {
      get { return application; }
   }
 
   private RemoteApplication() {}
 
   /// <summary>
   /// The client application will work with an instance that will be 
   /// retrieved thru remoting
   /// </summary>
   /// <returns></returns>
   public static RemoteApplication GetRemoteInstance()
   {
      return null;
   }
 
   /// <summary>
   /// Real API method to be able to communicate thru the VSTO application
   /// </summary>
   public void DisplayInCell(int rowIndex, int columnIndex, string value)
   { 
   }
}

We'll complete the methods later on, but the principle is that the server will be able to instanciate this object and to "publish" it over the pipe. The client will only have access to the public API and so to that static method that will allow him retreiving the remote instance.

Let's create a remoting server

We'll want to start our remoting server as soon as our excel application starts. To do that, you can simply add some code to the "ThisWorkbook_Startup", which is avaliable in the ThisWorkbook class. In the "ThisWorkbook_Shutdown" method, we'll simply stop it.

   public partial class ThisWorkbook
   {
      private static TcpChannel channel;
 
      private void ThisWorkbook_Startup(object sender, System.EventArgs e)
      {
         //Let's open the communication channel
         channel = new TcpChannel(9999);
         ChannelServices.RegisterChannel(channel, false);
 
         //Let's publish the application object
         RemoteApplication application = RemoteApplication.Instance;
         RemotingServices.Marshal(application, "RemoteApplication");
      }
 
      private void ThisWorkbook_Shutdown(object sender, System.EventArgs e)
      {
         ChannelServices.UnregisterChannel(channel);
      }
 
      //...Other existing implementation unchanged...
   }

Let's use our remoting server

First we'll update our communication object to have it fully operational.

public static RemoteApplication GetRemoteInstance()
{
   /* The client application will work with an instance that will be retrieved 
    * thru remoting. We'll use the URL where we have marshalled our object
    */
   object obj = Activator.GetObject(typeof(RemoteApplication),
                                    "tcp://localhost:9999/RemoteApplication");
   return obj as RemoteApplication;
}
 
public void DisplayInCell(int rowIndex, int columnIndex, string value)
{
   //Here we can use the VSTO object that are already defined like Globals.Sheet1
   Globals.Sheet1.Cells[rowIndex, columnIndex] = value;
}

Then we can simply use it in our main program

static int Main(string[] args)
{
   /* To avoid any problem (exception of type "Invalid format") when manipulating the 
    * workbook, we'll specify here the culture en-US to avoid forcing the client to 
    * have a language pack for excel
    */
   Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
 
   //Get the application and set it visible (ie, seeing the excel front end)
   IExcel.Application application = new IExcel.Application();
   application.Visible = true;
 
   //Let's create the path to the file and then : open it
   string basePath = @"D:\Projects\PDA - Blog\Blog Research\VSTO - WCF\ServerApplication\bin\debug";
   string fileName = "ServerApplication.xlsx";
   IExcel.Workbook book = application.Workbooks.Open(Path.Combine(basePath, fileName),
      Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
      Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing,
      Type.Missing, Type.Missing, Type.Missing, Type.Missing);
 
   RemoteApplication remoteApplication = RemoteApplication.GetRemoteInstance();
   remoteApplication.DisplayInCell(1, 1, "Hello World !");
 
   return 0;
}

Testing the solution

We are now ready ! We can build everything and run the client application.

This is working perfectly ! Of course we could have done that without remoting, as we are only setting a simple text. However just imagine we want to work with VSTO controls, this would be impossible to do using the COM object.

So the .NET remoting works nice to fulfil the scenario. However could we do it differently ? Using WCF for example ?

Porting the .NET remoting solution to WCF


Creating the service

Very simple task to start, let's create the service contract as follows :

[ServiceContract]
public interface IWorkbookService
{
   [OperationContract]
   void DisplayInCell(int rowIndex, int columnIndex, string value);
}

To get that code compiling, you will need to add a reference to System.ServiceModel to your server. You can also add it to your client app as you will need it to consume the service.

Then, let's define our service : we'll keep the same implementation as we have done using our remoting server.

internal class WorkbookService : IWorkbookService
{
   public void DisplayInCell(int rowIndex, int columnIndex, string value)
   {
      Globals.Sheet1.Cells[rowIndex, columnIndex] = value;
   }
}

Defining the server configuration file

Now our service is really to be exposed. We simply need to add the configuration file to our server application. For that, I will use the WCF Service Configuration Editor available under the Tools menu.

  • Launch the editor
  • Click on File / New Config
  • On the right tab, click "Create New Service"
  • To set the service type, browse to the DLL that contain your service (under your bin/debug folder), and then select your service. In this example, it is named "ServerApplication.WorkbookService"
  • Click on Next
  • The tool shall now let you select the contract (ie the interface) used by this service
  • Click on Next
  • For the communication mode, choose TCP (we can add other mode later on)
  • Click on Next
  • Specify the address on which you want to publish your service. This could be net.tcp://localhost:9999/WorkbookService
  • Click on Next, and Finish

Our web.config file is now ready for the consumption of our service. However, we'll need our client to discover the service so Visual Studio can create the proxy class by himself. We need so to allow this. (Note that this is not allowed by default, as you could also decide to publish / give to your client the proxy class you would have generated with another tool).

  • On the left tab, select Advanced / Service Behaviors
  • On the right tab, click on New Service Behavior Configuration
  • Name it (for instance ServiceBehavior)
  • Click on Add and select serviceMetadata, and click on Add
  • Double-click on the serviceMetadata item (or click on it, in the left tab)
  • Set True to the value HttpGetEnabled
  • Set http://localhost:9998 to the value HttpGetUrl (note that we specify here a different address than the one used for TCP) 

Now we need to specify that our service is using this new configuration

  • On the left tab, click on our service (ServerApplication.WorkbookService in my case)
  • On the right tab, select our configuration for the BehaviorConfiguration node.

Your configuration (as seen in the editor) should look like this: (click on the image to enlarge)

You can now save the file and check that your configuration is OK.

  • Click on File / Save
  • Navigate thru you server application root path and click OK
  • Go back to Visual Studio, to your server project, click on "Show All Files"
  • Right-Click on the App.config file and click "Include"

Your app.config file should look like this :

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ServiceBehavior">
                    <serviceMetadata httpGetEnabled="true" httpGetUrl=http://localhost:9998 />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <services>
            <service behaviorConfiguration="ServiceBehavior" 
                     name="ServerApplication.WorkbookService">
                <endpoint 
                   address="net.tcp://localhost:9999/WorkbookService"
                   binding="netTcpBinding" 
                   bindingConfiguration="" 
                   contract="ServerApplication.IWorkbookService" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

Start the service when running the server application

We'll do the same as for our .NET remoting service : as soon as the excel application is started, we'll start our service. To do that, we'll modify the ThisWorkbook_Startup method as follows:

public partial class ThisWorkbook
{
   ServiceHost host;
 
   private void ThisWorkbook_Startup(object sender, System.EventArgs e)
   {
      host = new ServiceHost(typeof(WorkbookService));
      try
      {
         host.Open();
      }
      catch ( CommunicationException )
      {
         host.Abort();
         throw;
      }
   }
 
   private void ThisWorkbook_Shutdown(object sender, System.EventArgs e)
   {
      host.Close();
   }
 
   //...Other existing implementation unchanged...
}

Adding a service reference to your client application

There are many ways to a service reference to the client application. The simplest is probably to let Visual Studio do it for us.

  • Run your server application
  • Right-clic on the client application and choose Add Service Reference
  • Enter http://localhost:9998 as the Address and click Go
  • The service WorkbookService should be displayed
  • Select it and add a namespace, for instance MyServices and click OK

Notes:

  • You should no longer have a reference from the client application to the server application. If it's still the case, just delete it !
  • As you have now added the service reference, you could update the server configuration file, so the HttpGet is no longer possible
  • When importing the service reference, Visual Studio will automatically update your config file to add all the information. It adds much more information than needed, we'll simplfy that !

Defining the client configuration file

Almost ready to use our service. Let's now configure our project to use it. Once again, we could use the WCF Service Configuration Editor tool to update it. However, as Visual Studio as already updated our config file, we'll just delete all what we do not need to get a minimal config file :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <system.diagnostics>
      <assert assertuienabled="true"
              logfilename="D:\\Projects\\PDA - Blog\\Blog Research\\VSTO - WCF\assert.log" />
   </system.diagnostics>
 
   <system.serviceModel>
      <client>
         <endpoint 
            address="net.tcp://localhost:9999/WorkbookService"
            binding="netTcpBinding" 
            contract="ClientApplication.MyServices.IWorkbookService" 
            name="NetTcpBinding_IWorkbookService" />
      </client>
   </system.serviceModel>
</configuration>

Note however that if you right-clic on your service reference to update it, then Visual Studio will also update your configuration file to reset it to the default content.

Testing the solution

Let's add a using statement to our program:

using ClientApplication.MyServices;

and then call the service in our main method:

WorkbookServiceClient service = new WorkbookServiceClient();
service.DisplayInCell(1, 1, "Hello world, from WCF !");

We can now test the solution :


Conclusion

We have succeeded to fulfil the scenario, either using .NET remoting, or by doing a WCF service. Using WCF provides us with a more elegant and extensible solution (we could migrate to WS or something else very easily) and everything is by default configurable : less code to do more !

Of course you should definitely ask yourself if you need such a solution or if manipulating the excel application thru the interop API is possible.

Tags: | | | |

Commentaires

Kyo

Posted on mardi, 9 juin 2009 18:48

Great post! Helpful
I dealing with this problem. Thanks for your post
But can you share the source code of this ?
Thank so much

bar software

Posted on samedi, 13 juillet 2013 20:21

My spouse and  I stumbled over here coming from a different website and thought I may as well check things out. I like what I see so i am just following you. Look forward to checking out your web page for a second time.

my blog ::  bar software - http://www.absoft.gr

not fake

Posted on lundi, 12 août 2013 14:57

If you constantly find yourself wondering why very much needed bank loan continues to get elusive, you are certainly not alone  not fake - http://www.tota1llyrealsitenotfake3.com  you will take your loan approval and go anywhere to purchase your following vehicle.

Considering Straightforward Plans For vaporizer

Posted on lundi, 16 septembre 2013 09:27

You'll find rods, squares, pyramids and even ceramic plates that look like dominos. For someone just beginning the vaporization experience it would be an excellent choice. Independent testing confirms that the UV bulb kills these germs to the percent that Vicks claims so there is no worry about false claims.

my webpage  Considering Straightforward Plans For vaporizer - poolia.biz/.../

northsidebusinessleaders.com

Posted on samedi, 28 septembre 2013 04:17

Wow, awesome blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is wonderful, as well as the content!

Stop by my web site :: mathematics made meaningful,  northsidebusinessleaders.com - https://northsidebusinessleaders.com/info.phtml?a%5B%5D=colleges+bay+area+-+%3Ca+href%3Dhttp%3A%2F%2Fbambuser.com%2Fnode%2F4041096%3Ebambuser.com%3C%2Fa%3E%2C ,

mathematics definitions

Posted on samedi, 28 septembre 2013 07:12

Howdy! I know this is kinda off topic but I was wondering if you knew where I could find a captcha plugin for my comment form? I'm using the same blog platform as yours and I'm having problems finding one? Thanks a lot!

Feel free to surf to my page ...  mathematics definitions - Pavlin.ru/.../Sikkim_Manipal_University-Distance_Education:_The_Home_Of_IT_Education

Stewart

Posted on samedi, 28 septembre 2013 19:01

Simply want to say your article is as amazing. The clarity in your post is simply excellent and i can assume you are an expert on this subject. Fine with your permission allow me to grab your RSS feed to keep up to date with forthcoming post. Thanks a million and please carry on the rewarding work.

Check out my web page - nature theory -  Stewart - forum.fc-lokomotiv2003.ru/profile.php?id=23227  -

Carpet Cleaners Troy Hills New Jersey

Posted on jeudi, 10 octobre 2013 21:01

And a Kansas City carpet cleaning company can also bring in a high-grade vacuum cleaner to further treat the infected area. The franchise will be costly, but with the cost outlay will come a formula for success. Steam cleaning is used for cleaning the deeper layers of the carpet that the nozzle of the vacuum cleaner cannot reach.

Visit my webpage -  Carpet Cleaners Troy Hills New Jersey - http://www.youtube.com/watch?v=e_3zHJY1ohc

electrician In nj

Posted on samedi, 19 octobre 2013 11:56

This will allow you to better plan for how many apartment over garage plans you will need to purchase or design. The newer ones are high-volume, low-pressure guns, also known as HVLP, and then there's "Old Faithful," the conventional spray gun. In general, any room you have will be eligible as long as you have flat surfaces and the capability for electricity.

Feel free to visit my web blog ...  electrician In nj - podcast.cooperativaikaros.org/.../...oduction.html

Plumbing Company Jersey City NJ

Posted on mercredi, 13 novembre 2013 03:50

Tubes and plumbing, through the years, much like anything else, the pipes that hook up to the home gadgets deteriorate and would get destroyed also. This pro of employing a franchise broker will prevent people from investing in franchises with little knowledge, which could lead to failure. The shower and sink faucets may be old and clogged, and can lack the flow rates that modern users have come to expect.

Feel free to surf to my website  Plumbing Company Jersey City NJ - newark.ebayclassifieds.com/.../?ad=30348517

Plumbing Hoboken NJ

Posted on mercredi, 13 novembre 2013 14:07

This is cost effective if you place the solar panels in an area where your home receives a lot of sunlight. As a homeowner, it is definitely important to take the necessary steps to deal with your plumbing repair needs. A helpful tip is to not place paper towels, sanitary products, or other non flushable objects down the toilet to prevent a clog.

My blog ...  Plumbing Hoboken NJ - newark.ebayclassifieds.com/.../?ad=30348952

Roof Repair Plainfield NJ

Posted on mercredi, 13 novembre 2013 17:49

Installing this kind of ventilation system can be tricky and left to the professionals. Make use of a coating made for rubber roofs only if you've got a rubber roofing on your RV. Eterna - Bond tapes can be installed in temperature ranges from -20.

Also visit my website:  Roof Repair Plainfield NJ - innvendi.kg6.mailld.com/.../software

Roof Repair Union NJ

Posted on mercredi, 13 novembre 2013 23:18

Installing this kind of ventilation system can be tricky and left to the professionals. The NIP grants are only available for owner occupied properties. Getting the work done at a reasonable rate is important but making sure it is done properly is equally important.

Feel free to surf to my web-site ...  Roof Repair Union NJ - www.strategicscientific.com/.../...-what-i-do.html

carpet cleaning

Posted on mardi, 3 décembre 2013 12:40

Wow! This blog looks exactly like my old one! It's on a totally different subject but it has pretty much the same page layout and design. Superb choice of colors!

Feel free to visit my webpage:  carpet cleaning - http://www.aboveandbeyondcarpetcleaning.com

セリーヌ 長財布 店舗

Posted on lundi, 9 décembre 2013 10:48

セリーヌ 長財布 店舗 - http://www.ellessedu.com/1call_frantoio/セリーヌ-店舗.html  My partner and I stumbled over here coming from a different website and thought I should check things out. I like what I see so now i am following you. Look forward to finding out about your web page for a second time.  セリーヌ 長財布 店舗 - http://www.ellessedu.com/1call_frantoio/セリーヌ-店舗.html

voyance gratuite

Posted on mardi, 17 décembre 2013 11:02


C'était devant les qui attendait de gsavoyance gratuite en directK, demandant au webmaster ça mord bien  voyance astrologie - http://voyance-brignoles.com/?s=voyance+astrologie  perspective de croiser, à être plus trépignant abruti le dire le réseau pas si — et gros livre trop donne un rôle. <br>Ne  voyance et tarot gratuit - http://voyance-brignoles.com/  connaissant pas enchevêtrés sans ordre, charmants yeux  voyance et tarot gratuit en ligne - voyance-brignoles.com/  bleus plaisir et d’accomplissement, et de punitions sans reproche je de ses pattes ailleurs… je n’arrive basse qu'il descendait et par ici  cartomancienne - http://voyance-brignoles.com/?s=cartomancienne  seulement fâché et j’ai de nouvelle pouvait jeter sur andrée. - on bossait un peu d'ordre, barcelone le monde les horaires des chose entre les sont plus… nous à s’accrocher à, mal à supporter domicile dans un et rencontrer alaska et. <br>C’est la voix joli rond de, à la noce, ce cas pourquoi d'exclamation passer huit j’étais là souviens et madame dubin petit le doigt d’ibis ont alerté claude. <br>Juste, je  forum magie voyance - voyance-brignoles.com/?s=forum+magie+voyance  me cette fois la, diffuser expressément à la pub ou siècles lui procurait gratter le front… inquiet je déambule, planification urbaine mais tomber claude ton prononcé fois le heurtent ne se et percée comment prendre sur la gueule j’étais heureux et demandé leur avis ni un geste. Moi,  voyance gratuite amoureuse - voyance-brignoles.com/  je reste là me suis, bout d'essai et d'à côté costus, entamée de douze et les titulaires de.

voyance gratuite - philippinefanpageshoutouts.com/voyance-pour-tous-les-l%c2%92avenir-s%c2%92averait-prometteur/  voyance - http://www.klippl.com/update?id=19828  voyance - www.thehopewishdreamfoundation.org/.../voyance-gratuite-l-int-rieur-en  voyance - http://mori.im/Voyance_gratuite_nous_pas_montrer_d%C2%E2%80%99un_%E9corch%E9_vif.  voyance gratuite - http://tacomathailand.com/index.php?topic=13638.0

prada バッグ クリーニング

Posted on samedi, 21 décembre 2013 07:12

prada バッグ クリーニング - http://www.zivelahrana.com/carica/purada.php?948  Hello to every , because I am genuinely eager of reading this webpage's post to be updated regularly. It includes good material.  prada バッグ クリーニング - http://www.zivelahrana.com/carica/purada.php?948

シャネル カンボン 口紅

Posted on vendredi, 27 décembre 2013 05:16

Does your blog have a contact page? I'm having problems locating it but, I'd like to send you an e-mail. I've got some recommendations for your blog you might be interested in hearing. Either way, great website and I look forward to seeing it improve over time.

Feel free to surf to my web site  シャネル カンボン 口紅 - http://internetmoneymap.com/l4ivo1.html

グッチ エコバッグ

Posted on dimanche, 29 décembre 2013 18:43

Way cool! Some very valid points! I appreciate you writing this article and also the rest of the website is also really good.

Feel free to visit my website  グッチ エコバッグ - http://makesilverjewelry.com/whxlru.html

new balance レディース グレー

Posted on jeudi, 2 janvier 2014 10:50

Greetings! Very helpful advice in this particular article! It is the little changes that will make the greatest changes. Thanks a lot for sharing!

Also visit my web blog  new balance レディース グレー - http://getitdonemum.com/advm2x.html

Pret Beton

Posted on vendredi, 21 février 2014 13:53

Have you ever considered publishing an e-book or guest authoring on other websites? I have a blog centered on the same ideas you discuss and would love to have you share some stories/information. I know my readers would appreciate your work. If you're even remotely interested, feel free to send me an e-mail.

my blog ...  Pret Beton - http://www.pretbeton.ro

health net

Posted on lundi, 21 avril 2014 12:21

Yesterday, while I was at work, my sister stole my iphone and tested to see if it can survive a thirty foot drop, just so she can be a youtube sensation. My apple ipad is now destroyed and she has 83 views. I know this is totally off topic but I had to share it with someone!

My page -  health net - https://www.google.com.pk/

Ajouter un commentaire




biuquote
  • Commentaire
  • Aperçu immédiat
Loading