📜 ⬆️ ⬇️

Portable distribution of .Net applications with Microsoft Report Viewer and Oracle Instant Client reports



Quite often there is a need or desire to abandon the creation of the installer and distribute the application by copying the folder with files to the target computer. If you're wondering how to create a portable distribution of .Net applications with Report Viewer reports or how to portable copy client and driver to access the Oracle database, please use the cat. I will try to explain everything in detail.

As a practical part, the creation of an application that displays reports for the SuperMag trading system (which, in fact, uses the Oracle database) will be considered.

One of the advantages of Oracle's portable distribution is that its standard installation is not the most pleasant thing to do. And if you also install the Report Viewer, the installation process on several machines risks becoming a tedious task. Of course, as an alternative, you can use ClickOnce, but when distributing with ClickOnce, you can also copy the folder with libraries as it is done in this example.
')
Required libraries for desktop applications with portable access to Oracle

You need to download Oracle Instant Client. If you are using a localized application, then it is better for you to take a package of basic, although it takes about 100Mb in size, but, unlike the “light” (30 MB) light, it guarantees you to work with Cyrillic.

Unpacks the archive and take 2 files from it:

oci.dll (abbreviation for Oracle Call Interface);
orannzsbb11.dll or orannzsbb12.dll (if you use version 12).

A third file is also needed:

If you took the version of basic, then it will be - oraociei11.dll or oraociei12.dll (again for version 12).
If you have taken the version of light - oraociicus11.dll or oraociicus12.dll (I’m not going to mention that the second file for version 12 is understandable to everyone).

And Oracle Data Provider - ODP.NET is also required (it is better to take the XCopy version, it is smaller), unpack and find 2 files:

Oracle.DataAccess.dll ;
OraOps11w.dll or OraOps12w.dll (this file is required by Oracle.DataAccess.dll for working with Oracle Instant Client files, it is the same for .Net 2.0 and for .Net 4.0).

If you want to use version 12, you can download both the Oracle Instant Client and ODP in one ODAC file (Oracle Data Access Components) at: www.oracle.com/technetwork/topics/dotnet/downloads/index.html

Required libraries for portable Report Viewer application

For the portable distribution of the Report Viewer, let's take the 2010 version.

Version 2013 as a version for portable distribution, I did not take. After I discovered that Microsoft.ReportViewer.ProcessingobjectModel.dll is missing in it, I had a “template break” and I decided that they would be quite satisfied with the reports of 2010. If you know how to create a portable distribution of the 2013 version, I am waiting for your comments. One could even announce a contest, but bad luck - there is no prize.

Download Microsoft Report Viewer Redistributable 2010 and unpack the exe file as an archive.
Among the unpacked files we find reportviewer_redist2010core.cab .
We continue the "matryoshka" and unpack in turn this file.
Find the files and rename them as follows:

FL_Microsoft_ReportViewer_Common_dll_117718_117718_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8
rename to Microsoft.ReportViewer.Common.dll

FL_Microsoft_ReportViewer_Processingobject_125592_125592_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8
rename to Microsoft.ReportViewer.ProcessingobjectModel.dll

FL_Microsoft_ReportViewer_WebForms_dll_117720_117720_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8
rename to Microsoft.ReportViewer.WebForms.dll

FL_Microsoft_ReportViewer_WinForms_dll_117722_117722_x86_ln.3643236F_FC70_11D3_A536_0090278A1BB8
rename to Microsoft.ReportViewer.WinForms.dll

If the developer's computer has the Oracle Client and Report Viewer installed (and it must be installed necessarily), links to these files can even be set, and simply copy them into folders from the exe (into the Debug and Release folders of the project). But Oracle.DataAccess.dll must be placed in the project folder and set the "Copy Locally" property to one of the two copying options.

In order for the fear of portable distribution not to be so large, I will briefly describe the library search algorithm of the desktop .Net application.

Before you start the dll search, the system checks whether the dll is already loaded into memory, and whether the dll is already in the list of already known dlls (try to view the list in the registry at HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ KnownDLLs ).

The dll search algorithm depends on some factors (for example, on whether the SafeDllSearchMode mode is set), but approximately this is (in the example SafeDllSearchMode is turned off):

1. The directory from which the application is launched;
2. Current directory;
3. System directory. You can use the GetSystemDirectory function to get the path to this directory;
4. 16-bit system directory;
5. Windows directory. You can use the GetWindowsDirectory function;
6. Folders that are listed in the system variable PATH.

What is the system variable PATH and where it is located, see clearly in the following screenshot:



We now turn to the practical part.

In a WPF application (and for me, XAML is more preferable than obsolete Forms as a layout editor), the Report Viewer component is added using the Windows FormsHost control.

<WindowsFormsHost HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" Name="windowsFormsHost1"> <rv:ReportViewer x:Name="_reportViewer"/> </WindowsFormsHost> 

To make the example not look useless, we will get data from the document history in it.
To store the data required by the report, create a class:

  public class ReportDataSM { public DateTime EventTime { get; set; } public string Doc { get; set; } public string UserName { get; set; } public string NewState { get; set; } } 

Now we can create a template file for the appearance of our report (For a visual editor, the Microsoft SQL Server Data Tools option should be selected when installing Visual Studio).

Add a report file to the project (Reporting). Set the value of the "Copy Local" property to "Copy if newer." Then open the newly created report file and go to the “Data Sources” tab. Click “Add new data source”, select “Object” type, open our project in the list of objects and find our ReportDataSM class in it, which we select:



Next, to display the rows with data, add a table to the report. When adding a dataset property to a query, select our data source and give it some name (in the example DataSet1):



This data source will be specified in the table table in the DataSetName property. The rdlc file can be edited not only with the help of the visual editor, but also with the help of the XML editor (this way it is convenient to delete old unnecessary data).

We set the columns to the values ​​from the data of our array (clicking on the lines is simple and intuitive, I will not explain in detail).

Consider the code

Since freezing the interface is a very bad practice, we will start fetching the data in a separate thread. For data storage, we use the competitive generic collection of the ReportDataSM class. Add to variable declarations (at the beginning of the class):

 ConcurrentBag<ReportDataSM> list4R; 

After initializing it somewhere in the code:

 list4R = new ConcurrentBag<ReportDataSM>(); 

Once we decided to receive data without blocking the interface, we will need a delegate to call the method asynchronously:

 delegate void MyGetDataDelegate(string s); 

In this example, the delegate takes one string value as a parameter.
The parameter will be the ID of the document for which you want to receive data on the change history.
A method that is a delegate implementation, let it be a method called getDataMethod.
I cite the simplified code for the implementation of the data acquisition method (without processing possible errors):

  void getDataMethod(string docid){ DataSet dataset = new DataSet(); string oradb = "Data Source=" + "DATABASENAME" + ";User Id=" + "typeusernamehere" + ";Password=" + "password123" + ";"; //  ,        ,      Oracle //   TNSNAMES.ORA  ,     . using (OracleConnection conn = new OracleConnection(oradb)) { if (conn.State != ConnectionState.Open) conn.Open(); string sqltext = "select SMDocLog.EVENTTIME, SMDocLog.ID, SMDocLog.USERNAME, NVL(SSDocStates.DOCSTATENAME,' ') as NEWSTATE"; sqltext = sqltext + " from SMDocLog LEFT JOIN SSDocStates"; sqltext = sqltext + " ON SMDocLog.DOCTYPE=SSDocStates.DOCTYPE and SMDocLog.NEWSTATE=SSDocStates.DOCSTATE"; sqltext = sqltext + " WHERE ID='" + docid + "'"; sqltext = sqltext + " ORDER BY EVENTTIME DESC"; using (OracleCommand cmd = new OracleCommand()) { cmd.Connection = conn; cmd.CommandText = sqltext; cmd.CommandType = CommandType.Text; using (OracleDataAdapter adapterO = new OracleDataAdapter(cmd)) { using (DataSet ds = new DataSet()) { adapterO.Fill(ds); //    ,        foreach (DataRow dr in ds.Tables[0].Rows) { ReportDataSM rd = new ReportDataSM(); rd.EventTime = Convert.ToDateTime(dr["EVENTTIME"]); rd.UserName = dr["USERNAME"].ToString(); rd.Doc = dr["ID"].ToString(); rd.NewState = dr["NEWSTATE"].ToString(); list4R.Add(rd); } } // end using DataSet } // end using OracleDataAdapter } // end using OracleCommand } } 

Call this method asynchronously with:

  MyGetDataDelegate dlgt = new MyGetDataDelegate(this.getDataMethod); IAsyncResult ar = dlgt.BeginInvoke(txtDocN.Text, new AsyncCallback(CompletedCallback), null); 

Pay attention to the CompletedCallback method, which is passed as a parameter.
Its implementation is necessary in order to run some other method after the completion of our callable method. In our case, after extracting the data, you need to bind this data to the report and update the interface.

  void UpdateUserInterface() { _reportViewer.Reset(); ReportDataSource reportDataSource= new ReportDataSource("DataSet1", list4R); //            _reportViewer.LocalReport.ReportPath = "ReportSM.rdlc"; _reportViewer.LocalReport.DataSources.Add(reportDataSource); _reportViewer.RefreshReport(); } 

But we have a catch in that we can update the interface only from the application flow.
To do this, we will call the update method of the application interface from the dispatcher thread. That is, the contents of our CompletedCallback method will be:

 void CompletedCallback(IAsyncResult result) { Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new NoArgDelegate(UpdateUserInterface)); } 

Again, to call the method asynchronously, we needed a delegate, which this time does not accept any parameters. Add it to the beginning of our code:

 private delegate void NoArgDelegate(); 

It should get something like this application:



Update: link to GitHub project (using Oracle.ManagedDataAccess installed from NuGet package manager)

Source: https://habr.com/ru/post/242965/


All Articles