In our website, we are using the ReportViewer control to display SSRS reports. When the user clicks the link for a report, the code stuffs some initial variables to present the proper report, i.e.:
reportViewerNew.ServerReport.ReportServerUrl = new Uri(ReportURL);
reportViewerNew.ServerReport.ReportPath = ReportPath + ReportName;
Every Report has been created with at least one Parameter called "userName". On the report this is a hidden Report Parameter, and the value gets passed down into the Stored Procedure that populates the report so we can track who is requesting the information.
So, in the code we have a Switch block to load up an Array (the length depends on which Report is being called upon) that has at the very least this "userName":
default:
RptParameters = new ReportParameter[1];
RptParameters[0] = new ReportParameter("userName", UserID);
break;
With the array established, we then pass the pre-loaded values into the Report Parameters through the SetParameters method:
reportViewerNew.ServerReport.SetParameters(ReportParameters());
This all works great, and when the report runs I can check our database and see that the "userName" value has been passed down to our tracking.
However... this stops working if the report requires some interaction by the user to run the report -- for example choosing an option in one dropdown loads up a dependent dropdown, choosing a date that loads a dropdown with related choices, etc. It seems that this "internal postback" of the report is causing the Hidden Report Parameter "userName" to loose its value, and what then gets passed down is the account we use for the database connection.
I have placed a break-point in the code on the if (!Page.IsPostBack) line to catch the Report's "postback" but this is not being triggered... it seems to be internal to the ReportViewer control. I have tried looking up some kind of EventHandler for this postback in the documentation (https://learn.microsoft.com/en-us/dotnet/api/microsoft.reporting.webforms) but not seeing it.
Does anyone know why the Report Parameter would be losing its value, how to prevent it, or if I need to be handling this in a different way?
We are doing the exact same thing but using an Mvc.ViewUserControl wrapper to use the control in an MVC Project. Adding to the MVC project requires us to turn off Async rendering, but these steps may still work in your case, if not with a little tweaking.
We still set the parameters in the Page_Load method, so that the intial report call has the "userName" parameter set, but we also inject the parameter on every postback call like this:
Create a delegate method for the "SubmittingParameterValues" Report Viewer event:
private void ReportViewerNew_SubmittingParameterValues(object sender, ReportParametersEventArgs e)
{
ReportParameter userNameParam = e.Parameters.Where(rp => rp.Name.ToLower().Equals("username")).FirstOrDefault();
if (userNameParam != null)
{
userNameParam.Values.Clear();
userNameParam.Values.Add(UserID); // Assign your value here
}
}
Assign the delegate function to the report viewer in the Page_Init function. This will intercept the parameters every time before the report is sent to the server:
protected void Page_Init(object sender, EventArgs e)
{
reportViewerNew.SubmittingParameterValues += ReportViewerNew_SubmittingParameterValues;
}
That's it!
Related
I am working on a WebApplication, and including a Telerik-Report in it. The normal way as described works well.
Now I'm trying to load the report definition from a Database, instead of having a file (request from the boss). So far, I've made it work with a temp-file, code is below. But this is far from nice coding.
My question: can I somehow give a string or stream to the report (instead of a file)?
My current code looks like this:
private readonly string __path = "C:\\my-temp-directory\\Reports\\";
protected void Page_Load(object sender, EventArgs e) {
string contents = /** Get XML from Database **/;
File.WriteAllText(__path + "temp.trdx", contents); // <-- This file I want to omit.
if (!IsPostBack) {
this.reportViewer1.ReportSource.Identifier = "temp.trdx";
this.reportViewer1.ReportSource.IdentifierType = IdentifierType.TypeReportSource;
}
}
Thanks.
Well, the report definition is just an XML so it doesn't really matter where you will obtain it from. Looking at the code I think it won't work, because you have a file, but are using TypeReportSource instead of UriReportSource when setting the IdentifierType.
In this scenario I think you should go with the CustomReportSource. The IdentifierType can be TypeReportSource, UriReportSource and CustomReportSource. The first two won't work in your case, because you don't know the report type and you also do not want to save it to a file. A CustomReportSource will allow you to put your own logic that will fetch the report from the database and send it to the engine. Actually there is a docs article that fits exactly your scenario:
How to Implement a Custom Report Source Resolver
I am writing a database management GUI for a company. There are several forms and they are binding to a database created from Access. Forms are like Employer, Vacancies and so on.
I am writing a function to generate and print all Vacancies information. One Vacancy each page. I am using some Currency manager to control where to find the vacancyID from Vacancy table.
// get the Vacancy record matching the Vacancy ID from the Vacancy record
int aVanID = Convert.ToInt32(drVanReport["VacancyID"].ToString());
cmVacancy.Position = DM.VacancyView.Find(aVanID);
//As soom as the app run at this line, ERROR HAPPENED.
DataRow drVan = DM.dtVacancy.Rows[cmVacancy.Position];
Then I use Graphics g = e.Graphics; g.drawing to generate my document for print preview.
private void btnVacancies_Click(object sender, EventArgs e)
{
if (vacancies == null)
{
vacancies = new Vacancies(DM, this);
}
vacancies.ShowDialog();
//ERROR POINTED TO HERE! SAYING Object reference not set to an instance of an object.
}
This error should not happen?? My codes is not actually about opening this form, although my button is on this form. This form is surely existing and opened successfully just now. Why is it null now?? I used try..catch..throw it too. But my document still cannot generate any data for print preview.
Can someone tell me how should I adjust my code to avoid this error and generate printing preview successfully? Thanks.
Maybe only some one who has ever done similar application could understand the context.
I have a web page which returns a set of results which you can then ask for in a .csv format.
As the creation of the file is quite lengthy (at times up to 30 minutes), I have added some JavaScript that adds a class to a div so that it covers the screen, to tell users that the report is being created and to be patient.
After the file has been created and downloaded I would like the div to then return to its original state of not being there (so to speak).
Here is what I currently have.
JavaScript
function skm_LockScreen() {
var lock = document.getElementById('ctl00_ContentPlaceHolder1_skm_LockPane');
var lock2 = document.getElementById('ctl00_ContentPlaceHolder1_pleaseWait');
if (lock)
lock.className = 'LockOn';
if (lock2)
lock2.className = 'WaitingOn';
}
function skm_UnLockScreen() {
var lock = document.getElementById('ctl00_ContentPlaceHolder1_skm_LockPane');
var lock2 = document.getElementById('ctl00_ContentPlaceHolder1_pleaseWait');
if (lock)
lock.className = 'LockOff';
if (lock2)
lock2.className = 'WaitingOff';
}
Button
<asp:Button ID="LatestReportButton" runat="server" CssClass="standardButton" Text="Latest Quote Report" Width="140px"
OnClick="ReportButton_Click" CommandArgument="2" OnClientClick="skm_LockScreen()" />
Code behind
protected void ReportButton_Click(object sender, EventArgs e)
{
Page.ClientScript.RegisterStartupScript(base.GetType(), "unlock", "<script type=\"text/javascript\">skm_UnLockScreen();</script>");
try
{
//Start creating the file
using (StreamWriter sw = new StreamWriter(tempDest + "\\" + csvGuid + ".csv", true))
{
//Code to create the file goes
}
}
catch
{
}
Response.AddHeader("Content-disposition", "attachment; filename=report.csv");
Response.ContentType = "application/octet-stream";
Response.WriteFile("CreatedReport.csv");
Response.End();
}
The issue I'm having is that the JavaScript is never written back to the page because of the Response.End();
I've already done it as two buttons, one to create the report the other to download it, but my company would prefer it to be all in one.
Any suggestions?
Given the length of time needed to generate the report, the method you're proposing contains a number of inherent risks, each of which will cause the report creation to fail:
the user may accidentally close the browser / tab;
the browser / tab might crash;
timeouts may occur on the web server.
I would tackle this problem in a different way. In the first instance, I would look to see if the report generation can be optimized - "30 minutes" rings alarm bells immediately. Assuming database(s) are involved at some point, I would investigate the following as a minimum:
Are database indexes being used, or used correctly?
Do the report generation queries contain inefficient operations (CURSORs, for example)?
Do the execution plans reveal any other flaws in the report generation process?
If this isn't a option (let's assume you can't modify the DBs for whatever reason), I would still consider a totally different approach to generating the report, whereby the report creation logic is moved outside of the web application into, say, a console app.
For this, you would need to define:
a way to pass the report's parameters to the report generation app;
a way to identify the user that requested the report (ideally login credentials, or Windows identity);
a way to notify the user when the report is ready (email, on-screen message);
a directory on the server where the reports will be saved and downloaded from;
a separate page from which the user can download the report.
A couple of database tables should be sufficient to log this information.
I am trying to deploy Crystal Reports in my MVC application. To get full use of the Crystal Report Viewer, I have to use a webform, which is working fairly well in my dev environment.
The application will be deployed on the user's servers and connect to their personal dbs. This means I do not have the final connection information when designing the report or the application.
I am able to successfully connect using their entries in the web.config file, load a DataTable with the report information, and pass it to the report. However, the report is still asking for the db credentials. The report is set to connect to my db, so it asks for my credentials and will not proceed without them. However, the final report shows the correct info from their db.
I am not sure if I need to change something in the report, or in the code behind. This is how I am setting the report ReportSource now:
protected void Page_Load(object sender, EventArgs e)
{
string strReportName = System.Web.HttpContext.Current.Session["ReportName"].ToString();
try
{
ReportDocument rd = new ReportDocument();
string strRptPath = Server.MapPath("~/") + "Rpts//" + strReportName;
rd.Load(strRptPath);
SqlParameter[] sqlParams = {};
DataTable testDt = DBHelper.GetTable("rptInvtDuplDesc", sqlParams);
rd.DataSourceConnections.Clear();
rd.SetDataSource(testDt);
CrystalReportViewer1.ReportSource = rd;
}
else
{
Response.Write("<H2>Nothing Found; No Report name found</H2>");
}
}
How do I prevent the report from asking for the original credentials?
EDIT:
If I pass the login to my db like this:
rd.SetDatabaseLogon("username", "password");
I do not get the db login again. The credentials have to be for the db used to create the report, but the displayed results are from the DataTable populated in the method above. If it has the data it needs from the current db, why does it need to connect to the original db?
EDIT2:
I have 2 data sources for this report. One is a table from the db and the other is the result from a stored procedure. I have now learned that is the cause of the extra login.
Have a look at my Blog post here on this.
There are a couple of actions required, but the main one is to create a new TableLogOnInfo instance, and then apply is using ApplyLogOnInfo.
You have to provide credentials for each datasouce in the report.
Ideally, the report will have a single datasource. If this is not possible, you need to provide credentials for each, or data for each.
Something like this works well if you want to provide the data:
rd.Database.Tables[0].SetDataSource(testDt);
rd.Database.Tables[1].SetDataSource(micssys);
Otherwise, something like this will allow the report to access the db directly for each datasource:
rd.SetDatabaseLogon("username","password}");
I see a lot of people coming up with some excessive ways to change the folder location on the fly with flajaxian multiple file upload control.
Was just wondering if the more experienced could take a look at the way I've come up with and let me know if there are any major issues I should be concerned about. (Assuming I have the proper error checking in place.)
I planned on initializing the control as seen below. :
<cc1:FileUploader ID="FileUploader1" runat="server" OnFileReceived="fileUploader_FileReceived" RequestAsPostBack="true">
</cc1:FileUploader>
(I RequestAsPostBack="true" as there are some other controls I need to check in my event handler)
I simply change the HttpFileCollection.SaveAs property in the fileUploader_FileReceived event. Since flajaxian does this one file upload at a time, we can expect that there is only 1 file in the collection (or else we could use a loop).
protected void fileUploader_FileReceived(object sender,
com.flajaxian.FileReceivedEventArgs e)
{
HttpFileCollection files = Request.Files;
// Change path to whichever folder I need
String TempFileName = "C:\\NEW\\PATH\\TO\\Folder\\" + files[0].FileName;
// Save the file.
files[0].SaveAs(TempFileName);
}
This implementation seems to work great as long as the folder is existing! I was just wondering if there is anything technically wrong with an implementation like this, again , assuming all error checking was in place.
Thanks!
A better way to do this would be to use an adapter, and over write the folder location in the
OnFileNameDetermining event. This way, we also get all the goodies with the adapter.
<cc1:FileUploader ID="FileUploader1" runat="server"` OnFileReceived="fileUploader_FileReceived" RequestAsPostBack="true">
<Adapters>
<cc1:FileSaverAdapter runat="server" FolderName="Ups" OnFileNameDetermining="fileUploader_FileDetermined" />
</Adapters>
</cc1:FileUploader>
In the file determined event, we can change the folder location programatically
protected void fileUploader_FileDetermined(object sender, com.flajaxian.FileNameDeterminingEventArgs e)
{
e.FileName = "C:\\NewFolder\\" + e.File.FileName;
}
We can use the FileReceived event to check if the folder exists, and if not, create it.
protected void fileUploader_FileReceived(object sender, com.flajaxian.FileReceivedEventArgs e)
{
int fileIndex = e.Index;
if (fileIndex == 0)
{
// We are on our first file, check if the new folder exists, if not, create it
}
}
What you are doing is fine, although, if you are saving files within the web site, consider using the MapPath method to create a physical folder from a virtual path within the web site
MapPath("/Images/User1")
This my mininal APSX implementation
<fjx:FileUploader ID="FileUploader1" runat="server" OnFileReceived="FileUploader2_FileReceived">
</fjx:FileUploader>
No adapters or folder is specified. When the FileRecevied event fires, I save files to a folder based on the Forms Authentication user name (names do not use characters not allowed in folder names).
Also note that the FileReceivedEventArgs has a reference to the (HTTP) file
e.File
The FileUploader control will show all files processed - you can even set the status code (e.g. 550) if there is an error, which is returned to the client.
Note that, the server call to the FileReceived event does not occur inside a nornal page postback, even if you specify
RequestAsPostBack="true"
So, a PagePreRender does not take place.
The only issue is, how do you perform any other processing at the client after the uploads complete (e.g. showing images uploaded).
Work I have in progress to this end is to use the client side event
FileStateChanged
When the last file is processed
if (file.state > Flajaxian.File_Uploading && isLast) {
I use JQuery to click a hidden submit button. The postback looks through session values stored when the files were saved, and renders back the images into a DIV.
However, an immediate submit causes issues with empty session inside the FileReceived event for some reason (I assume because the internal asynchronous call back has not completed). A pause of a few seconds before initiating the postback works OK.