I've got a bunch of reports deployed as RDL's to a SSRS. Because of high security requirements, the db passwords change very frequently. It's become a huge task to keep up with the changes and having to modify dozens upon dozens of reports. This leads to my question...
Is it possible to programmatically set a data source or a connection string for a deployed report?
Could this be done using an app that modifies something in the report itself as it sits on the server?
Could this be done by modifying a shared data source from an app as the DS sits on the server?
Could it be done by embedding a script inside the report itself which retrieves the connection from say a web service?
Thanks
This can be done in multiple ways, I think one of the simplest is using the API of SSRS web service. The web service lets you manipulate all the reporting entities including the Data Sources.
As a solution to this issue it's possible to update the data sources credentials using calls to the web service every time the DB password changes (even automate it using some sort of trigger?).
I did something similar in a project where RDLs and data sources had to be generated, deployed and manipulated in runtime and it worked fine so updating data source must be feasible.
Add a Service Reference to your project for your Reporting Service endpoint(http://ReportServerHost.mydomain.tld/ReportServer/ReportService2005.asmx). Use the following code to modify the Data Source password:
public static void ChangeDataSourcePassword(string dataSourcePath, string password)
{
using (ReportingService2005SoapClient reportingService = new ReportingService2005SoapClient())
{
reportingService.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
try
{
ServerInfoHeader serverInfo = null;
DataSourceDefinition dataSourceDefinition = null;
serverInfo = reportingService.GetDataSourceContents(dataSourcePath, out dataSourceDefinition);
dataSourceDefinition.Password = password;
serverInfo = reportingService.SetDataSourceContents(null, dataSourcePath, dataSourceDefinition);
}
catch (FaultException ex)
{
// Do something with the exception. Rethrow it and/or show it to the user.
Console.WriteLine(string.Format("Failed to change the password on {0}: {1}", dataSourcePath, ex.Message));
throw new ApplicationException("Failed to change the password on the Data Source.", ex);
}
}
}
Related
First of all, please excuse me if it sounds too rooky. I consider myself novice in MVC applications.
I have ran into a strange problem and there does not seem to be any way out of this, at least so far..I have looked everywhere and left no stone unturned to get it worked. Finally I turned to this forum.
This is my first post, so any mistakes please overlook and guide me.
The problem is multifaceted...
The High Level Details...
I have created a C# ASP MVC Web Application waiting to be uploaded on a Remote Server (Client Machine)
The application uses Entity Framework - Code First approach
Connects to the database with Windows Authentication system
Scene1: Where the application worked
I have tested the application on my machine and it worked flawlessly.
I have Express edition of Sql Server Management Studio installed on my system.
Scene2: Where the application failed. The Problem - Big Picture
It works great on my system but while testing it on the Remote Server it crashes
The application fails to connect to the Remote Server Sql Database. As soon as it tries to connect to the database, it crashes with an error message "Login failed for user '<UserName>."
I have checked everything in the connection string - like -
Data source name is correct
Initial Catlog also points at the correct database name
Integrated Security = true
There is no UserID or password mentioned
Connection string worked great on my system. But it does work on the Client Machine and shows the error above.
Connection string is:
connectionString="Data Source=RemoteComputerName;Initial Catalog=DatabaseName;Integrated Security=True; MultipleActiveResultSets=true"
I am not able to figure out exactly what is causing the error - Is it my code or Is it the Sql Server database settings - permissions.
Since the connection worked on my local machine, which means the code is correct
In order to check whether sql server permissions are working..I have created partial 'test connection application' in WINFORM and uploaded on the Server, this time the code works and read all the table data.
but when I try to connect in MVC project it shows the error..."Login failed for user...".
I'm totally confused what works in WINFORM fails in MVC.
Database permissions must be right because when tried to access it using WINFORM it worked.
please let me know if I have missed to provide any details in this post.
Any help is highly appreciated!!!
Thank You.
Please show your connection string. (you can block out any real passwords, actual server names, actual db names).
Since you mention "Integrated Security = true"..........then you have to be aware of which Identity is running the process (the website or the winforms.exe)
When running as winforms app, you are running as "you" (most likely). As in, the logged in user to the windows o/s.
When running under IIS, you are running under the Identity associated with the App Pool you are using..which most likely is NOT you, but rather a service account.
Your service-account does not have access to the sql-server.
You can read in depth here: https://support.microsoft.com/en-us/help/4466942/understanding-identities-in-iis#:~:text=ApplicationPoolIdentity%3A%20When%20a%20new%20application,also%20a%20least%2Dprivileged%20account.
You can show this......by catching an exception, and then something like this:
You can replace ArithmeticException with whatever, I'm just showing "adding info" to a caught exception and rethrowing.
try
{
// some sql server access code
}
catch(Exception ex)
{
throw new ArithmeticException("What is the real IIdentity: " + this.FindIIdentity(), ex);
}
private string FindIIdentity()
{
try
{
//'Dim user As WindowsPrincipal = CType(System.Threading.Thread.CurrentPrincipal, WindowsPrincipal)
//'Dim ident As IIdentity = user.Identity
string returnValue = string.Empty;
WindowsIdentity ident = WindowsIdentity.GetCurrent();
returnValue = ident.Name;
try
{
returnValue += " on " + System.Environment.MachineName;
} catch (Exception ex)
{}
return returnValue;
} catch (Exception ex)
{
return "Error Finding Identity";
}
}
IIS screenshots
Is there a common way to recover from a connection error in MongoDB with the C# driver?
Currently, my Windows service shuts down if MongoDB is turned off. I currently have my app structured like this at the start of my Windows service:
//Set up connections for Mongo
var con = new MongoConnectionStringBuilder(ConfigurationManager.ConnectionStrings["MongoDB"].ConnectionString);
var client = new MongoClient(con.ToString());
var server = client.GetServer();
var db = server.GetDatabase(con.DatabaseName);
I then inject the db object into my repositories.
I'm trying to find something like an event handler or a condition I could listen to in my whole application to prevent from crashing the entire service should mongo go down for some reason.
As suggested by the driver document, the MongoClient is added to manage Replica Set stuff, which earlier or later you will need. To avoid mass code refactoring then, you need to make better use of it now.
The MongoClient, which is thread-safe, have implemented the failover logic among replica set nodes already. It's supposed to be singleton along with your application domain. Thus you can inject the MongoClient, other than db (which is not even thread safe).
So always retry the GetServer() and GetDatabase() from MongoClient, and try/catch the exceptions produced by them would finally give you the available db object when MongoDB is online again.
The point is, MongoDB will not notify the clients about its online, so there's no such event to notify you, either. You'll have to keep trying in your client side until it's ok. And to avoid the exceptions to bring down your service, you'll have to catch them.
EDIT: I am wrong about the thread-safety according to the document. However, it doesn't change the fact you shouldn't store MongoDatabase for future migration to replica set.
In addition to yaoxing answer, wanted to do show code piece to solve this issue.
var client = new MongoClient(connString);
var server = client.GetServer();
while (server.State == MongoServerState.Disconnected)
{
Thread.Sleep(1000);
try
{
server.Reconnect();
}
catch (Exception ex)
{
Debug.WriteLine("Failed to connect mongodb {0} Attempt Count: {1}",
ex, server.ConnectionAttempt);
}
}
So I can't reference any specific SSRS web service with my project because I want users to be able to point it to their own report server. Is there any way for me to do this?
Specifically I'm going to need to be able to (at least) get the available reports, view reports, and schedule reporting. I haven't touched report scheduling via c# yet, but report selection/viewing is simple enough with a reference to my local reporting service. Is there any way to dynamically load the web service for any given reporting service--assuming I know the url or the server and instance name?
var rs = new ReportingService2005
{
Credentials = System.Net.CredentialCache.DefaultCredentials
};
// get catalog items from the report server database
CatalogItem[] items = rs.ListChildren("/", true);
// add each report to the combobox
foreach (CatalogItem ci in items)
{
if (ci.Type == ItemTypeEnum.Report)
comboBox1.Items.Add(ci.Path);
}
This is what I'm currently doing to pull out the available reports. But, as mentioned, above, it is directly referencing my own report server. ReportingService2005 (what I named the reference to my .asmx web service) is what I would like to be able to dynamically load.
Definitely possible. ReportingServices have .Url property you can assign. So you can do:
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
rs.Url = "http://<Server Name>/reportserver/ReportService2005.asmx";
Reference: "Setting the Url Property of the Web Service"
Today, for each customer, we deploy same SSRS reports folder and data source folder.
The difference between these folders are the name of each folder and the connection string of the data source.
We are using Report Server 2008 R2.
Is it possible to maintain only one reports and data source folder and change programmatically its connection string on server-side before the report been rendered?
If not, Is it something that can be achieved by changing some logic in reports?
Today we use "shared data source" option.
This is something we've done in our environment - we maintain one set of reports that can be deployed at any client with their own configuration.
You've got a couple of options here. Since you're using a Shared Data Source this makes things easier as you won't need to define a Data Source for each report.
1. Use the rs.exe utility and a script file
rs.exe at Books Online
This program allows you to create script files (in VB.NET) that can interact with a Report Server Web Service. You create a script file (e.g. Deploy.rss) and call the rs.exe program with various parameters, including any custom ones you define:
rs.exe -i DeployReports.rss -s http://server/ReportServer -v DatabaseInstance="SQL" -v DatabaseName="ReportDB" -v ReportFolder="ClientReports"
So this would call a script DeployReports.rss, connect to http://server/ReportServer, with three user defined parameters which could be used to create a data source and the report folder.
In the scipt file you could have something like this:
Public Sub Main()
rs.Credentials = System.Net.CredentialCache.DefaultCredentials
CreateFolder(reportFolder, "Report folder")
CreateFolder(datasourceFolder, "Data source folder")
CreateDataSource()
End Sub
Which can then make Web Service calls like:
rs.CreateFolder(folderName, "/", Nothing)
'Define the data source definition.
Dim definition As New DataSourceDefinition()
definition.CredentialRetrieval = CredentialRetrievalEnum.Integrated
definition.ConnectString = "data source=" + DatabaseInstance + ";initial catalog=" + DatabaseName
definition.Enabled = True
definition.EnabledSpecified = True
definition.Extension = "SQL"
definition.ImpersonateUser = False
definition.ImpersonateUserSpecified = True
'Use the default prompt string.
definition.Prompt = Nothing
definition.WindowsCredentials = False
Try
rs.CreateDataSource(datasource, datasourcePath, False, definition, Nothing)
Console.WriteLine("Data source {0} created successfully", datasource)
Catch e As Exception
Console.WriteLine(e.Message)
End Try
You haven't specified what version of Reporting Services you're using, so I'm assuming 2008. Please note that there are multiple endpoints that can be used, depending on SQL Server version. The 2005/2008 end point is deprecated in 2008R2 and above but is still usable. Just something to bear in mind when writing your script.
2. Call the SSRS Web Service through an application
Report Server Web Service overview
The same calls that are made from the script above can be made in any other application, too. So you'd just need to add a reference to a Report Server Web Service through WSDL and you can connect to a remote service and call its methods to deploy reports, data sources, etc.
So ultimately you're connecting to the Report Server Web Service, it's just the medium used that you need to think about.
Using a script is easier to get running as it's just running a program from the command line, but writing your own deployment application will certainly give greater flexibility. I would recommend getting the script going, so you understand the process, then migrate this to a bespoke application if required. Good luck!
You can use an Expression Based Connection String to select the correct database. You can base this on a parameter your application passes in, or the UserId global variable. I do believe you need to configure the unattended execution account for this to work.
Note: be careful about the security implications. Realize that if you would pass sensitive data (e.g. passwords) into a parameter, that (a) it will go over the wire, and (b) will be stored in the execution log tables for reporting services.
I am trying to switch different databases for a web application at Run time.
Senario
We have one asp.net web application and different databases for different customers.I am trying to switch particular connection string value from a common database where i am keeping a mapping table for connection string ,particular customer id and password .After the successful lo gin i am piking a connection string from the common database and edit the web.config file connection string section by replacing selected connection string at run time.
i am doing this by add following code to login event
conectionString = cString;
Configuration openWebConfiguration = WebConfigurationManager.OpenWebConfiguration("~");
ConnectionStringsSection sections = openWebConfiguration.GetSection("connectionStrings") as ConnectionStringsSection;
if (sections != null)
{
sections.ConnectionStrings["ConnectionStringName"].ConnectionString = conectionString;
ConfigurationManager.RefreshSection("ConnectionStringName");
openWebConfiguration.Save();
}
i am reading above connection string on a page by using ConfigurationManager.problem is the web config file is changing but after calling to another page using Response.Redirect will throw an exception .Exception is "Unable to evaluate expression because the code is optimized or a native frame is on top of the call stack "I can realized this is something happen on cross threaded environment.My questions are
What is exact reason for above exception?
which page life circle of the Asp.net reads the setting from the web config file ?
What is the proper way i can implement above scenario?
I am wondering why this question seems still unanswered.OK i have found some answermy self.It may be wrong but some how they are giving some meaning to me.I assume following are acceptable for my knowledge level.
1)I don't know the exact reason ,but this is something happen because of code is modified while an application running
2)Based on my search WEB Config file is started to read by the application when the IIS server start.So what ever values to be modified inside WEB Config ,require to restart the IIS server to load them in to memory.We can modify the connection string dynamically but still the application will run on the previous connection string.So we need t restart the IIS to load newer one.
Note:Modify a existing connection string is different than add a new connection string to a WEB Config.
3)I have used a common data Base where i have authentication details for different different connection strings for Several database.WeB config has the connection string for above master database.If an user gives his authentication detail it will select his connection string and load it as new connection string .So the remaining process will be based on that connection string.
Any new arguments for above answers are highly appreciable.I need corrections from other developers because i am very eager to learn.