Service & Data Contracts
SsrsRenderStudio.Wcf.ServiceContract.IRenderStudioUserService
Method | Purpose |
ExecutionResult PrintReport(ExecutionContext context) | Sends a report to a printer destination |
ExecutionResult EmailReport(ExecutionContext context) | Sends a report to an email destination in PDF format |
bool ReprocessReport(Guid Id) | Quickly reprocess an already processed report |
ExecutionContextMember | Type | Definition | | |
FriendlyReportName | string | Name of the report to print. Must match master configuration. |
Destination | string | Either the printer name or email addresses delimited with a semicolon. Printer name must match master configuration. |
ReportParameters | string [] | Report execution parameters. An even numbered string array of values. First value being the parameter name and the second being the parameter value. (e.g. string[] {"param1", "abc", "param2", "123"}) |
ClientInformation | string[] | Any user defined array of strings to include for logging purposes |
ExecutionResultMember | Type | Definition |
IsSuccess | bool | Was execution an overall success. |
ExecutionId | Guid | Can be used to reprocess the report. |
ReportExpiration | DateTime | When the report expires from cache. |
ServiceSideException | EnumServiceSideException | An enumerated exception value. |
ServiceSideExceptionAddl | string | Additional exception details. |
Sample Client Code
public ReportSubmitResponse SubmitReport(ReportSubmitRequest report)
{
ExecutionResult result = null;
IRenderStudioUserService proxy = null;
CustomClientFactory<IRenderStudioUserService> factory = null;
string endpointConfiguration = "*";
bool success = false;
bool abort = false;
bool shouldAttemptRecovery = false;
bool isPrintDestination = false;
string userDestination = "";
// todo: determine the destination and the output type
// prepare context
string[] a = new string[report.ReportParameters.Length + 4];
string[] b = new string[] { "ReportingService", report.EmployeeDivision, report.EmployeeBadge, report.ReportFriendlyName };
b.CopyTo(a, 0);
report.ReportParameters.CopyTo(a, b.Length);
string[] clientInformation = a;
ExecutionContext context = new ExecutionContext(report.ReportFriendlyName, userDestination, report.ReportParameters, clientInformation);
// keep track of the first endpoint in the config pointer so we don't loop endlessly through the binding configs
System.ServiceModel.Description.ServiceEndpoint firstEndpoint = null;
do
{
// reset loop flag
shouldAttemptRecovery = false;
try
{
// submit report
factory = new CustomClientFactory<IRenderStudioUserService>(endpointConfiguration);
if (!factory.ClientConfiguredSuccessfully)
{
shouldAttemptRecovery = true;
throw new Exception("Failed to configure client");
}
if (firstEndpoint == null)
firstEndpoint = factory.Endpoint;
else
if (firstEndpoint.Address.Uri == factory.Endpoint.Address.Uri)
throw new Exception("Client tried all possible bindings and could not establish a connection. Aborting.");
try
{
Logger.Log(EnumLoggingSeverity.DEBUG, "ReportingServiceImpl: attempting " + factory.Endpoint.Address.Uri.ToString());
}
catch (Exception ex1)
{
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: Factory failed to retrieve a binding configuration pointer.");
}
proxy = factory.CreateChannel();
if (isPrintDestination)
result = proxy.PrintReport(context);
else
result = proxy.EmailReport(context);
success = result.IsSuccess;
}
catch (CommunicationException ex)
{
abort = true;
shouldAttemptRecovery = true; // we should always try to recover on a comm error
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: CommunicationException, " + ex.Message);
}
catch (TimeoutException ex)
{
abort = true;
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: TimeoutException, " + ex.Message);
}
catch (Exception ex)
{
abort = true;
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: Exception, " + ex.Message);
}
finally
{
if (proxy != null)
{
if (abort)
((IClientChannel)proxy).Abort();
else
((IClientChannel)proxy).Close();
}
}
} while (shouldAttemptRecovery);
return new ReportSubmitResponse { IsSuccess = success };
}
Another sample client code
namespace ReportingService
{
//todo: document
// adding a division:
// a DEFAULT user needs to be created in the db for that division to handle all reports when a specific user does not exist. preferably with a print destination
// if the DEFAULT user does not exist then the report will fail!
// a DEFAULT report record needs to be created in the db for each division
// add record with userid and friendlyreportname="*", this will handle any and all reports for that user
// destination types:
// PRINT, EMAIL
// adding users:
// inititally there are no users in the DB, any report submitted will print to the DEFAULT user destination
// add record to user and report table
// the report table needs to include records for all the supported ssrs reports
// this might be difficult to maintain, the other way would be to have a modules table such as the 02_ford_mixed_load module and definied the reports there, but actually that wouldn't work to set a print destination based on module because each individual report might need to go to different destionatins.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class ReportingServiceImpl : IReportingService
{
public ReportingServiceImpl()
{
/*
Database.SetInitializer(new DbContext_09_ReportingServiceConfiguration_Initializer());
DbContext_09_ReportingServiceConfiguration db = new DbContext_09_ReportingServiceConfiguration("DbContext_09_ReportingServiceConfiguration_production_seed_only");
db.Database.Initialize(true);
db.Dispose();
*/
}
public ReportSubmitResponse SubmitReport(ReportSubmitRequest report)
{
DbContext_09_ReportingServiceConfiguration db = new DbContext_09_ReportingServiceConfiguration();
ExecutionResult result = null;
IRenderStudioUserService proxy = null;
CustomClientFactory<IRenderStudioUserService> factory = null;
string endpointConfiguration = "*";
bool success = false; // the current report being processed
bool allSuccess = true; // every single report in this batch
bool abort = false;
bool shouldAttemptRecovery = false;
//******* query DB **********//
//*** get user
Model_09_ReportingServiceConfiguration_User db_user = db.GetUser(report.EmployeeDivision, report.EmployeeBadge);
if (db_user == null)
db_user = db.GetUser(report.EmployeeDivision, "DEFAULT");
if (db_user == null)
{
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: DEFAULT user in the [users] table for division '" + report.EmployeeDivision + "' does not exist! Unable to determine the destination. Unable to process report request.");
return new ReportSubmitResponse { IsSuccess = false };
}
//*** get report
//todo: process report to default user if necessary or wildcard destination, report record is null when a wildcard destination is selected
List<Model_09_ReportingServiceConfiguration_Report> db_reports = db.GetReports(db_user, report.ReportFriendlyName);
db.Dispose();
// add a destination for the user based on their email
if (db_reports.Count == 0)
{
Logger.Log(EnumLoggingSeverity.WARNING, "ReportingServiceImpl: " + report.ReportFriendlyName + " report is not explicitly defined for user " + db_user.Badge + ", division " + db_user.Division + ".");
db_reports.Add(new Model_09_ReportingServiceConfiguration_Report
{
DestinationFriendlyName = db_user.Email,
DestinationType = new Model_09_ReportingServiceConfiguration_DestionationType { DestinationName = "EMAIL" }
});
}
// override destination with one supplied in service call
//todo: only print override is supported.
if( !(string.IsNullOrEmpty(report.DestinationFriendlyName.Trim())) )
{
foreach(var r in db_reports)
{
//if (r.DestinationType.DestinationName == "PRINT")
//{
r.DestinationFriendlyName = report.DestinationFriendlyName.Trim();
r.DestinationType.DestinationName = "PRINT";
//}
}
}
// prepare context
string[] a = new string[report.ReportParameters.Length + 4];
string[] b = new string[] { "ReportingService", report.EmployeeDivision, report.EmployeeBadge, report.ReportFriendlyName };
b.CopyTo(a, 0);
report.ReportParameters.CopyTo(a, b.Length);
string[] clientInformation = a;
//todo: this needs to be rewritten
// if this service fails to fetch HTTP config or fails to write to disk then we're screwed and never get out of the loop
// for now we 're limiting the retries so we eventually skip the loop
int recoveryCount = 0;
int recoveryCountMAX = 10;
foreach (var db_report in db_reports)
{
ExecutionContext context = new ExecutionContext(report.ReportFriendlyName, db_report.DestinationFriendlyName, report.ReportParameters, clientInformation);
//todo: there should be a better way of doing this,
// if the admin puts the same endpoint twice then it will abort prematurely.
// also if a binding pointer config does not exist that could be a problem
System.ServiceModel.Description.ServiceEndpoint firstEndpoint = null;
WcfConfiguration.Reset();
recoveryCount = 0;
do
{
// reset loop flag
shouldAttemptRecovery = false;
try
{
// submit report
factory = new CustomClientFactory<IRenderStudioUserService>(endpointConfiguration);
if (!factory.ClientConfiguredSuccessfully)
{
recoveryCount++;
if (recoveryCount < recoveryCountMAX)
shouldAttemptRecovery = true;
throw new Exception("Failed to configure client");
}
if (firstEndpoint == null)
firstEndpoint = factory.Endpoint;
else
if (firstEndpoint.Address.Uri == factory.Endpoint.Address.Uri)
throw new Exception("Client tried all possible bindings and could not establish a connection. Aborting.");
try
{
Logger.Log(EnumLoggingSeverity.DEBUG, "ReportingServiceImpl: attempting " + factory.Endpoint.Address.Uri.ToString());
}
catch (Exception ex1)
{
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: Factory failed to retrieve a binding configuration pointer.");
}
proxy = factory.CreateChannel();
if (db_report.DestinationType.DestinationName != "EMAIL")
result = proxy.PrintReport(context);
else
result = proxy.EmailReport(context);
success = result.IsSuccess;
}
catch (CommunicationException ex)
{
abort = true;
shouldAttemptRecovery = true; // we should always try to recover on a comm error
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: CommunicationException, " + ex.Message);
}
catch (TimeoutException ex)
{
abort = true;
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: TimeoutException, " + ex.Message);
}
catch (Exception ex)
{
abort = true;
Logger.Log(EnumLoggingSeverity.ERROR, "ReportingServiceImpl: Exception, " + ex.Message);
}
finally
{
if (proxy != null)
{
if (abort)
((IClientChannel)proxy).Abort();
else
((IClientChannel)proxy).Close();
}
}
} while (shouldAttemptRecovery );
if (success)
{
//todo: cache it
// todo: maintain user cache
}
else
{
allSuccess = false;
}
}
return new ReportSubmitResponse { IsSuccess = allSuccess };
}
public ReportCacheResponse QueryCachedReports(ReportCacheRequest cache)
{
return new ReportCacheResponse
{
UserCache = new UserCacheObject[]
{ new UserCacheObject
{
ExecutionId = Guid.Empty,
Expiration = DateTime.Now,
FriendlyReportName = "Your cache is empty!"
}
}
};
}
public ReportSubmitResponse ProcessCachedReport(ReportCacheReprocessRequest execution)
{
throw new NotImplementedException();
}
}
}