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

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

by admin

Portable distribution of .Net applications with Microsoft Report Viewer and Oracle Instant Client reports
Quite often you need or want to forego creating an installer and distribute an application by copying the folder with the files to the target computer.If you are interested in how to create a portable distribution of the .Net application with Report Viewer reports, or how to copy the client and drivers to access the Oracle database portatively, please go here. I will try to explain everything in detail.
As a practical part, we will look at creating an application that displays reports for the SuperMag trading system (which actually uses an Oracle database).
One of the perks of a portable distribution of the Oracle client is that installing it as standard is not the most pleasant thing to do. And if you also install Report Viewer, the installation process on multiple machines risks becoming a tedious undertaking. Of course, you can use ClickOnce as an alternative, but when distributing with ClickOnce, it is also quite possible to copy the folder with the libraries as in this example.

Required libraries for a desktop application with Oracle portable access

You need to download Oracle Instant Client. If you use a localized application, you better take the basic package. Although it takes about 100Mb, but in contrast to the "light" (30 megabytes) package, it guarantees the work with Cyrillic.
Unpack the archive and take two files from it :
oci.dll (abbreviation for Oracle Call Interface);
orannzsbb11.dll or orannzsbb12.dll (if you will use version 12).
A third file is also needed :
If you took the basic version, it would be oraociei11.dll or oraociei12.dll (again for version 12).
If you took the light version – oraociicus11.dll or oraociicus12.dll (I won’t mention that the second file is for version 12, it’s already clear to everyone).
And you also need Oracle Data Provider – ODP.NET (better to take the XCopy version, – it is smaller), unzip and find 2 files :
Oracle.DataAccess.dll ;
OraOps11w.dll or OraOps12w.dll (this file is necessary for Oracle.DataAccess.dll to work 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 Oracle Instant Client and ODP in one ODAC (Oracle Data Access Components) file at : www.oracle.com/technetwork/topics/dotnet/downloads/index.html

Required libraries for the portable Report Viewer application

Let’s take the 2010 version of Report Viewer as the portable distribution.
I did not take the 2013 version as the portable distribution version. After discovering that it was missing Microsoft.ReportViewer.ProcessingobjectModel.dll, I had a "pattern break" and decided that 2010 reports would be fine. If you know how to create a portable distribution of the 2013 version, I’m waiting for your comments. I could even hold a contest, but there’s no prize.
Download Microsoft Report Viewer Redistributable 2010 and unzip the exe file as an archive.
Among the unzipped files we find reportviewer_redist2010core.cab
Continue the "matryoshka" and unpack this file in turn.
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 you have Oracle Client and Report Viewer installed on the developer’s computer (and it must be installed), you can not even set the links to these files, but simply copy them to the folders with exe (in the Debug and Release project folders).But the Oracle.DataAccess.dll must be placed in the project folder and the "Copy locally" property must be set to one of the two copying options.
To make the fear of portable distribution not so great, I will briefly describe the algorithm of how a desktop .Net application searches for the library.
Before running a search for a dll, the system checks to see if the dll is already loaded into memory, and if the dll is in the list of already known dlls (try looking at the list in the registry at HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerKnownDLLs ).
The dll search algorithm depends on some factors (e.g. whether SafeDllSearchMode is set), but roughly it is as follows (in the example SafeDllSearchMode is off):
1. The directory from which the application runs;
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 specified in the PATH system variable.
To see what the PATH system variable is and where it is located, see the following screenshot :
Now let’s get to the practical part.
In a WPF application (and for me XAML is preferable to legacy Forms as a layout editor), the Report Viewer component is added using the WindowsFormsHost 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, let’s get the data in it from the history of the document.
To store the data the report needs, let’s 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 of the appearance of our report (For the visual editor on the developer’s computer, the Microsoft SQL Server Data Tools option must be selected when installing Visual Studio).
Let’s add a report file to the project (Reporting – Report). Let’s set the "Copy locally" property to "Copy if newer". Then open the newly created report file and go to "Data Sources" tab. Click "Add a new data source", select the type of "Object" in the list of objects expand our project and find our class ReportDataSM, which we select:
Next, to display rows of data, let’s add a table to the report. When adding a dataset property to the query, select our data source and give it some name (DataSet1 in the example):
This data source will be listed in the tablex of the table in the DataSetName property. The rdlc file can be edited not only with the visual editor, but also with the XML editor (it is convenient to delete old unnecessary data this way).
Set the columns to values from our array data (by clicking on the rows – it’s simple and intuitive, I won’t explain in detail).

Consider the code

Since freezing the interface is a very bad practice, we will run the data sampling in a separate thread. We use the generic collection of class ReportDataSM to store the data. Let’s add to the variable declarations (to the beginning of the class):

ConcurrentBag<ReportDataSM> list4R;

After initializing it somewhere in the code :

list4R = new ConcurrentBag<ReportDataSM> ();

Since we decided to get the data without blocking the interface, we need a delegate to call the method asynchronously :

delegate void MyGetDataDelegate(string s);

In this example, the delegate takes a single string value as a parameter.
The parameter will be the ID of the document for which you want to get the change history data.
The method that is the implementation of the delegate, let it be a method named getDataMethod.
Here is a simplified code of the getDataMethod implementation (without handling possible errors):

void getDataMethod(string docid){DataSet dataset = new DataSet();string oradb = "Data Source=" + "DATABASENAME" + ";User Id=" + "typeusernamehere" + " Password=" + "password123" + ";"// in order not to specify the full database connection string, it is necessary that the folder of the Oracle client// the TNSNAMES.ORA file with the data necessary to connect to the base.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, 'Send mail') 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);// extracting data to the dataset and then reading it into our collectionforeach (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}}

We call this method asynchronously with :

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

Note the CompletedCallback method, which is passed as a parameter.
Its implementation is needed in order to run some other method after our called method finishes. In our case, after retrieving the data, we need to bind that data to the report and update the interface.

void UpdateUserInterface(){_reportViewer.Reset();ReportDataSource reportDataSource= new ReportDataSource("DataSet1", list4R);// specify the name of the report data source and our collection as data_reportViewer.LocalReport.ReportPath = "ReportSM.rdlc";_reportViewer.LocalReport.DataSources.Add(reportDataSource);_reportViewer.RefreshReport();}

But we have a catch in that we can only update the interface from the application flow.
To do this, we will call the method to update 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 need a delegate which this time takes no parameters. Let’s add it to the beginning of our code :

private delegate void NoArgDelegate();

You should get an application like this :
Update : link to GitHub project (using Oracle.ManagedDataAccess, installed from NuGet package manager)

You may also like