Changing Databases in Crystal Reports for .NET - asp.net

I have a problem which is perfectly described here (http://www.bokebb.com/dev/english/1972/posts/197270504.shtml):
Scenario:
Windows smart client app and the CrystalReportViewer for windows.
Using ServerFileReports to access reports through a centralized and disconnected folder location.
When accessing a report which was designed against DB_DEV and attempting to change its LogonInformation through the CrystalReportViewer to point against DB_UAT, it never seems to actually use the changed information.
It always goes against the DB_DEV info.
Any idea how to change the Database connection and logon information for a ServerFileReport ????
Heres code:
FROM A PRESENTER:
// event that fires when the views run report button is pressed
private void RunReport(object sender, EventArgs e)
{
this.view.LoadReport(Report, ConnectionInfo);
}
protected override object Report
{
get
{
ServerFileReport report = new ServerFileReport();
report.ObjectType = EnumServerFileType.REPORT;
report.ReportPath = #"\Report2.rpt";
report.WebServiceUrl = "http://localhost/CrystalReportsWebServices2005/ServerFileReportService.asmx";
return report;
}
}
private ConnectionInfo ConnectionInfo
{
get
{
ConnectionInfo info = new ConnectionInfo();
info.ServerName = servername;
info.DatabaseName = databasename;
info.UserID = userid;
info.Password = password;
return info;
}
}
ON THE VIEW WITH THE CRYSTAL REPORT VIEWER:
public void LoadReport(object report, ConnectionInfo connectionInfo)
{
viewer.ReportSource = report;
SetDBLogon(connectionInfo);
}
private void SetDBLogon(ConnectionInfo connectionInfo)
{
foreach (TableLogOnInfo logOnInfo in viewer.LogOnInfo)
{
logOnInfo.ConnectionInfo = connectionInfo;
}
}
Does anyone know how to solve the problem?

I know this isn't the programatic answer you're looking for, but:
One thing that helps with this sort of thing is not creating your reports connected to the database directly, but first you create a "Data Dictionary" for Crystal Reports (this is done in the Report Designer). Then you link all of your reports to this dictionary which maps the fields to the proper databases.
Done this way, you only have one place to change the database schema/connection info for all reports.
Also, in your report designer set the report to not cache the results (sorry I don't remember the exact option). Reports can either have their initial results included or not.

Don't you have to browse all the "databaseTable" objects of the report to redirect the corresponding connections? You'll find here my VB version of the 'database switch' problem ...

In your CrystalReportViewer object you should set
AutoDataBind="true"

Related

Creating Reports in ASP.Net with Entity Framework

We are looking to add Microsoft Reports - SSRS to one of our internal websites.
The database has all the reporting features installed.
The website is using Entity Framework 4 for all data.
I have been able to create a report using the old fashioned way of creating a DataSet (*.XSD) and this works well.
My question though, is it possible to utilise the existing Entity Framework in the site for the data required by the reports? Rather than having to re-invent the wheel and make a whole DataSet, along with relationships etc..
It's a website and not application, so this (http://weblogs.asp.net/rajbk/archive/2010/05/09/creating-an-asp-net-report-using-visual-studio-2010-part-1.aspx) doesn't seem to apply; I don't see the DataSource (in part 2 of the tutorial)
Update
As a side-note, we would like to steer clear of expensive third-party controls etc.
Also, another way to look at the issue might be to generate the *.XSD from the entity framework entity model; is this possible? It's not ideal though would get us up and running..
Below is a quick sample of how i set the report datasource in one of my .NET winForms applications.
public void getMyReportData()
{
using (myEntityDataModel v = new myEntityDataModel())
{
var reportQuery = (from r in v.myTable
select new
{
l.ID,
l.LeaveApplicationDate,
l.EmployeeNumber,
l.EmployeeName,
l.StartDate,
l.EndDate,
l.Supervisor,
l.Department,
l.Col1,
l.Col2,
.......,
.......,
l.Address
}).ToList();
reportViewer1.LocalReport.DataSources.Clear();
ReportDataSource datasource = new ReportDataSource("nameOfReportDataset", reportQuery);
reportViewer1.LocalReport.DataSources.Add(datasource);
Stream rpt = loadEmbededReportDefinition("Report1.rdlc");
reportViewer1.LocalReport.LoadReportDefinition(rpt);
reportViewer1.RefreshReport();
//Another way of setting the reportViewer report source
string exeFolder = Path.GetDirectoryName(Application.ExecutablePath);
string reportPath = Path.Combine(exeFolder, #"rdlcReports\Report1.rdlc");
reportViewer1.LocalReport.ReportPath = reportPath;
reportParameter p = new ReportParameter("DeptID", deptID.ToString());
reportViewer1.LocalReport.SetParameters(new[] { p });
}
}
public static Stream loadEmbededReportDefinition(string reportName)
{
Assembly _assembly = Assembly.GetExecutingAssembly();
Stream _reportStream = _assembly.GetManifestResourceStream("ProjectNamespace.rdlcReportsFolder." + reportName);
return _reportStream;
}
My approach has always been to use RDLC files with object data sources and run them in 'local' mode. These data sources are ... my entities! This way, I'm using all of the same business logic, string formatting, culture awareness, etc. that I use for my web apps. There are a some quirks, but I've been able to live with them:
RDLC files don't like to live in web projects. We create a separate dummy winform project and add the RDLC files there.
I don't show reports in a viewer. I let the user download a PDF, Word, or Excel file and choose to save or open in the native viewer. This saves a bunch of headaches, but can put some folks off, depending on requirements. For mobile devices, it's pretty nice.
Since you are not using SSRS, you don't get the nice subscription feature. You are going to build that, if required. In many ways, though, I prefer this.
However, the benefits are really nice:
I'm using all of the same business logic goodness that I've already written for my views.
I have a custom ReportActionResult and DownloadReport controller method that allows me to essentially run any report via a single URL. This can be VERY handy. It sure makes a custom subscription component easier.
Report development seems to go pretty quick, now that I only need to adjust entity partial classes to tweak a little something here or there. Also - If I need to shape the data just a bit differently, I have LINQ.
We too use SSRS as "local" reports. We create Views in SQL server, then create that Object in our application along with the other EF Domain Models, and query that object using our DbContext. We use an ASPX page and use the code behind (Page_Load) to get the data passed to the report.
Here is an example of how we query it in the Page_Load Event:
var person = MyDbContext
.Query<ReportModel>()
.Where(x => x.PersonId == personId)
.Where(x => x.Year == year)
.Select(x =>
{
PersonId = x.PersonId,
Year = x.Year,
Name = x.Name
});
var datasource = new ReportDataSource("DataSet1", person.ToList());
if (!Page.IsPostBack)
{
myReport.Visible = true;
myReport.ProcessingMode = ProcessingMode.Local;
myReport.LocalReport.ReportPath = #"Areas\Person\Reports\PersonReport.rdlc";
}
myReport.LocalReport.DataSources.Clear();
myReport.LocalReport.DataSources.Add(datasource);
myReport.LocalReport.Refresh();
The trick is to create a report (.rdlc) with a blank data source connection string, a blank query block and a blank DataSetInfo (I had to modify the xml manually). They must exist in file and be blank as follows:
SomeReport.rdlc (viewing as xml)
...
<DataSources>
<DataSource Name="conx">
<ConnectionProperties>
<DataProvider />
<ConnectString />
</ConnectionProperties>
<rd:DataSourceID>19f59849-cdff-4f18-8611-3c2d78c44269</rd:DataSourceID>
</DataSource>
</DataSources>
...
<Query>
<DataSourceName>conx</DataSourceName>
<CommandText />
<rd:UseGenericDesigner>true</rd:UseGenericDesigner>
</Query>
<rd:DataSetInfo>
<rd:DataSetName>SomeDataSetName</rd:DataSetName>
</rd:DataSetInfo>
now in a page event, I use a SelectedIndexChanged on a DropDownList, bind the report datasource as follows:
protected void theDropDownList_SelectedIndexChanged(object sender, EventArgs e)
{
if (theDropDownList.SelectedIndex == 0)
return;
var ds = DataTranslator.GetRosterReport(Int64.Parse(theDropDownList.SelectedValue));
_rvReport.LocalReport.ReportPath = "SomePathToThe\\Report.rdlc";
_rvReport.LocalReport.DataSources.Add(new ReportDataSource("SomeDataSetName", ds));
_rvReport.Visible = true;
_rvReport.LocalReport.Refresh();
}
You can use a WCF-Service as Datasource and so re-use your application data and logic for your report. This requires a SQL-server standard edition at least i believe. So no can do with the free SQL-express edition.
You can use LINQ with RDLC Report which is quite easy to use
LinqNewDataContext db = new LinqNewDataContext();
var query = from c in db.tbl_Temperatures
where c.Device_Id == "Tlog1"
select c;
var datasource = new ReportDataSource("DataSet1", query.ToList());
ReportViewer1.Visible = true;
ReportViewer1.ProcessingMode = ProcessingMode.Local;
ReportViewer1.LocalReport.ReportPath = #"Report6.rdlc";
ReportViewer1.LocalReport.DataSources.Clear();
ReportViewer1.LocalReport.DataSources.Add(datasource);
ReportViewer1.LocalReport.Refresh();

c:\windows\system32\inetsrv\Test.pdf is denied

I have a requirement to generate a pdf document on a button click on using visual web part- sharepoint 2010.
I am using the the open source library http://www.itextpdf.com/ for the same. I am able to execute the below code using the project type as a Windowns Application. But, when I want do it on a button click , I am receiving c:\windows\system32\inetsrv\Test.pdf' is denied. error.
Below is the code that I am using
public static void Main(string[] args) --Console Application
{
Console.WriteLine("PDF demo");
Document myDoc = new Document(PageSize.A4.Rotate());
try
{
PdfWriter.GetInstance(myDoc, new FileStream("Salman.pdf", FileMode.Create));
myDoc.Open();
myDoc.Add(new Paragraph("First pdf File made by Salman using Itext"));
}
catch (DocumentException ex)
{
Console.Error.WriteLine(ex.Message);
}
myDoc.Close();
}
}
But I want do the same on a button click event.
protected void pdfGenerator_OnClick(object sender, EventArgs e)
{
Document myDoc = new Document(PageSize.A4.Rotate());
try
{
PdfWriter.GetInstance(myDoc, new FileStream("Salman.pdf", FileMode.Create));---I get an error here
myDoc.Open();
myDoc.Add(new Paragraph("First pdf File made by Salman using Itext"));
}
catch (DocumentException ex)
{
Console.Error.WriteLine(ex.Message);
}
myDoc.Close();
}
}
Please help me , i dont understand the reason for the error. I am tyring it for the first time.
The problem is you are trying to create a PDF in a directory you do not have access to. This is because you are not specifying the directory to save the path; which causes it to default in the same location as the current working directory. In many cases, such as for ASP.NET; the default is the same as the location of the process. For ASP.NET, the process is w3wp.exe and resides in c:\windows\system32\inetsrv. Most identities that w3wp will run as (Network Service or AppPoolIdentity) do not have access to that directory. In fact, only system administrators do.
You need to save it somewhere else that the process has write permissions to here:
PdfWriter.GetInstance(myDoc, new FileStream("Janaki.pdf", FileMode.Create))
Should be something like:
PdfWriter.GetInstance(myDoc, new FileStream(#"C:\directoryIcanWriteTo\Janaki.pdf", FileMode.Create))
If you want to save it in the same place as the website, you would use HttpContext.Current.Server.MapPath:
PdfWriter.GetInstance(myDoc, new FileStream(HttpContext.Current.Server.MapPath("~/Janaki.pdf", FileMode.Create))
Regardless, the identity will still need write permissions to the directory if it doesn't have them. But you definitely shouldn't put them in inetsrv.
Just don't use "FileStream" which requires write permissions on the server. Replace it with MemoryStream and then flush its content on the user's browser.

Can't redirect to another page using ASP.NET and WF 4

I am using WF 4 with ASP.NET and as part of the workflow the system may need to redirect to other pages for the user to input additional information under certain circumstances. Once they have entered that information, the system needs to resume the workflow where it left off.
I have this code so far in the initial page that kicks off the process and an activity in the workflow that sets a bookmark.
static InstanceStore instanceStore;
static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
static Guid id;
protected void Page_Load(object sender, EventArgs e)
{
SetupInstanceStore();
}
protected void btnStartWorkflow_Click(object sender, EventArgs e)
{
app = Session["applicant"];
Dictionary<string, object> workflowInputs = new Dictionary<string, object>();
workflowInputs.Add("Applicant", app.Applicant);
WorkflowApplication workflowApplication = new WorkflowApplication(new IdentityCheckActivites.IdentityCheckWorkflow(), workflowInputs);
workflowApplication.InstanceStore = instanceStore;
//returning IdleAction.Unload instructs the WorkflowApplication to persist application state and remove it from memory
workflowApplication.PersistableIdle = (a) =>
{
return PersistableIdleAction.Persist;
};
workflowApplication.Unloaded = (a) =>
{
instanceUnloaded.Set();
};
workflowApplication.Completed = (a) =>
{
instanceUnloaded.Set();
};
workflowApplication.Persist();
id = workflowApplication.Id;
workflowApplication.Run();
Session["id"] = id;
workflowApplication.Idle = (a) =>
{
instanceUnloaded.Set();
};
instanceUnloaded.WaitOne();
var bookmarks = workflowApplication.GetBookmarks();
if (bookmarks != null && bookmarks[0].OwnerDisplayName == "CC")
{
workflowApplication.Unload();
Context.Response.Redirect("SecondPage.aspx");
}
Context.Response.Redirect("FinalPage.aspx");
}
private static void SetupInstanceStore()
{
instanceStore = new SqlWorkflowInstanceStore(#"Data Source=xxx;Initial Catalog=SampleInstanceStore;User Id=xxx;Password=xxx;Asynchronous Processing=True");
InstanceHandle handle = instanceStore.CreateInstanceHandle();
InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
handle.Free();
instanceStore.DefaultInstanceOwner = view.InstanceOwner;
}
This seems to work very well in that it persists the workflow to the database and if the bookmark is set I want to redirect to a second page for the user to enter more data.
This is the part of the code that I am having problems with: -
var bookmarks = workflowApplication.GetBookmarks();
if (bookmarks != null && bookmarks[0].OwnerDisplayName == "CC")
{
workflowApplication.Unload();
Context.Response.Redirect("SecondPage.aspx");
}
Context.Response.Redirect("FinalPage.aspx");
If there's a bookmark set, I redirect to an intermediary page, if not and no user intervention was necessary, the page will just redirect to the final page.
This works if the bookmark is set, but if not the workflowApplication.GetBookmarks() statement throws an exception telling me that the workflow has completed.
I can't seem to find a way to detect at this stage which state the workflow is in so that I can redirect to the relevant page.
Maybe I have the wrong idea in general, as much as I search though, I cannot seem to find a lot of guidance on this subject.
Any ideas?
Thanks,
Jim.
I don't think there is a way to directly determine if the workflow is completed from WorkflowApplication (except for catching and inspecting the exception that is thrown).
But you could set a flag in side your Completed delegate which is executed only if the there is no bookmark set and the workflow is completed. You could then check this flag before calling GetBookmarks().
Not sure if I understand exactly, but it seems that your page controller is looking at the state of the workflow to understand what page to redirect to? The problem is that the state may be non-existent if the WF instance has ended?
If the above is correct then perhaps the approach is wrong. A more appropriate approach might be to have a WCF WF service on AppFabric (correlated by session id) handle the website request directly. (If a user in a particular session visits the site, then the WF determines what page to render, and if the user hits a certain button, then send a WCF WF message using net pipe binding)
instead of
workflow.idle
you need
wfApp.PersistableIdle
and don't forget
instanceUnloaded.Set();

Is there any way to speed up crystal reports generation?

We are running a reporting web application that allows the user to select a few fields and a crystal report is generated based off of the fields selected. The SQL that is generated for the most complex report will return the data in < 5 seconds, however it takes the report and average of 3 minutes to run, sometimes longer causing a time out. We are running VS2010. The reports are basically set up out of the box with no real manipulations or computations being done, just displaying the data in a nice format. Is there anything we can try to speed it up, pre-loading a dummy report to load the dlls, some hack to make crystal run faster, anything?
EDIT: Code Added to show the databinding
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
string strFile = Server.MapPath(#"AwardStatus.rpt");
CrystalReportSource1.Report.FileName = strFile;
DataTable main = Main();
CrystalReportSource1.ReportDocument.SetDataSource(main);
CrystalReportViewer1.HasCrystalLogo = false;
CrystalReportSource1.ReportDocument.ExportToHttpResponse(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat, Response, false, "pmperformance");
}
}
private DataTable Main()
{
Guid guidOffice = Office;
CMS.Model.ReportsTableAdapters.ViewACTableAdapter rptAdapter = new CMS.Model.ReportsTableAdapters.ViewACTableAdapter();
Reports.ViewAwardedContractsDataTable main = new Reports.ViewAwardedContractsDataTable();
if (Office == new Guid())
{
IEnumerable<DataRow> data = rptAdapter.GetData().Where(d => UserPermissions.HasAccessToOrg(d.guidFromId, AuthenticatedUser.PersonID)).Select(d => d);
foreach (var row in data)
{
main.ImportRow(row);
}
}
else if (guidOffice != new Guid())
{
main = rptAdapter.GetDataByOffice(guidOffice);
}
else
{
main = new Reports.ViewACDataTable();
}
return main;
}
private Guid Office
{
get
{
string strOffice = Request.QueryString["Office"];
Guid guidOffice = BaseControl.ParseGuid(strOffice);
if (!UserPermissions.HasAccessToOrg(guidOffice, AuthenticatedUser.PersonID))
{
return Guid.Empty;
}
else
{
return guidOffice;
}
}
}
protected void CrystalReportSource1_DataBinding(object sender, EventArgs e)
{
//TODO
}
This may be a bit flippant, but possibly consider not using crystal reports... We had a fair bit of trouble with them recently (out of memory errors being one), and we've moved off to other options and are quite happy...
Here's what I would do:
Put clocks from the time you get the field choices from the user, all the way to when you display the report. See where your processing time is going up.
When you look at the clocks, there can be various situations:
If Crystal Reports is taking time to fill the report, check how you're filling it. If you're linking the report fields directly to your data table, CR is probably taking time looking up the data. I suggest creating a new table (t_rpt) with dynamic columns (Field1, Field2,..FieldN) and pointing your report template to that table. I don't know if you're already doing this.
If it's taking time for you to lookup the data itself, I suggest creating a view of your table. Even though a memory hog, this will make your lookup quick and you can delete the view once you're done.
If it's none of the above, let us know what your clocks show.
In terms of loading any large amount of data, you'll always want to use a stored procedure.
Outside of that, you WILL see a delay in the report running the first time the Crystal DLLs load. Yes, you can preload them as you mentioned and that will help some.

Postback problem for my custom control load wizard

I have some problem that happens when controls are loaded in init and it still doesn't help me to get proper postback event fired on time.
I am trying to create a rich wizard control that will enable switching, links with description, completely customized steps, integration of substeps - by using dynamic control load that is avoids standard asp.net wizard way of loading.
Idea is to have on left part navigation, on right part content, or substeps that are run from right part and that go over whole area.
Download source project
Ok, I re-read the question, and here is what you have to do. You have to re-load these controls on each postback, give them always the same "Id". This can be done in Page_Init or in Page_Load event. And of course, you have to re-attach event handlers on each post back.
Many thanks.. well i found the answer - id was the problem, in load control method. I was doing this wizard.. well most of things work now.
If someone is interested to see how does this works.. there are some updates:
public void LoadSplitViewControl(string path)
{
SwitchNavigationView(NavigationView.SplitView);
LastNavigationView = NavigationView.SplitView;
LoadControl(SplitControlLoader, path, "LoadedControlSplit");
}
public void LoadSingleViewControl(string path)
{
SwitchNavigationView(NavigationView.SingleView);
LastNavigationView = NavigationView.SingleView;
LoadControl(SingleControlLoader, path, "LoadedControlSingle");
}
public void LoadSingleViewControlAsClear(string path)
{
SwitchNavigationView(NavigationView.SingleView);
LastNavigationView = NavigationView.SingleView;
LoadControlAsClear(SingleControlLoader, path, "LoadedControlSingle");
}
private void LoadControl(PlaceHolder holder, string path, string ID)
{
UserControl ctrl = (UserControl)Page.LoadControl(path);
ctrl.ID = ID;
LastControlPath = path;
holder.Controls.Clear();
holder.Controls.Add(ctrl);
}
//as i am using steps loaded controls using splitview and substeps controls using single view sometimes viewstate will not be valid so error will be thrown but u can resolve this by using LoadSingleViewControlAsClear that will load below method.
private void LoadControlAsClear(PlaceHolder holder, string path, string ID)
{
UserControl ctrl = (UserControl)Page.LoadControl(path);
ctrl.ID = ID;
LastControlPath = path;
ctrl.EnableViewState = false;
holder.Controls.Add(ctrl);
}
/another cool idea i am using for such an wizard is that i am not using viewstate but rather session object for saving values collected over steps. My session object key is generated by authenticated username and pageguid - so u can have many loaded pages and each of them will handle different session object./
public Guid PageGuid
{
get
{
if (PageGuidField.Value == "")
{
var _pageGuid = Guid.NewGuid();
PageGuidField.Value = _pageGuid.ToString();
return _pageGuid;
}
return new Guid(PageGuidField.Value);
}
}

Resources