C# controller and service
The controller is a pass-through to the service. You probably don't need both.
namespace Controllers.Integration
{
[RoutePrefix("api/groupdocs")]
public class GroupdocsConnectorController : ApiController
{
private readonly ILog _log;
private readonly IGroupdocsConnectorService _groupdocsConnectorService;
public GroupdocsConnectorController(ILog log, IGroupdocsConnectorService groupdocsConnectorService)
{
_log = log;
_groupdocsConnectorService = groupdocsConnectorService;
}
[HttpPost, Route("prepareDocument/{assetId}")]
public HttpResponseMessage PostPrepareDocument([FromUri] Guid assetId)
{
if (ModelState.IsValid)
{
try
{
var groupDocsUserId = _groupdocsConnectorService.PrepareDocument(assetId);
return this.ReturnCreated(groupDocsUserId);
}
catch (LoggedException)
{
throw this.ThrowGeneralHttpException();
}
catch (Exception exception)
{
_log.Error(exception);
throw this.ThrowGeneralHttpException();
}
}
else
{
throw this.ThrowInvalidModelHttpException(ModelState);
}
}
[HttpPost, Route("saveAnnotatedDocument/{sectionId:guid}/studentUser/{studentUserId:guid}/learningActivity/{learningActivityId:guid}/grade/{gradeId:guid}")]
public HttpResponseMessage PostAnnotatedDocument(Guid sectionId, Guid studentUserId, Guid learningActivityId, Guid gradeId, AnnotatedDocumentRequest annotatedDocumentRequest)
{
if (ModelState.IsValid)
{
try
{
_groupdocsConnectorService.SaveAnnotatedDocument(sectionId, studentUserId, learningActivityId, gradeId,
annotatedDocumentRequest.UpdateGradeRequest, annotatedDocumentRequest.SubmissionExpectationAssets);
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
catch (LoggedException)
{
throw this.ThrowGeneralHttpException();
}
catch (Exception exception)
{
_log.Error(exception);
throw this.ThrowGeneralHttpException();
}
}
else
{
throw this.ThrowInvalidModelHttpException(ModelState);
}
}
}
}
The service interface
----
namespace Service.Contract.Integration
{
public interface IGroupdocsConnectorService
{
string PrepareDocument(Guid assetId);
void SaveAnnotatedDocument(Guid sectionId, Guid studentUserId, Guid learningActivityId, Guid gradeId, UpdateGradeRequest updateGradeRequest, SubmissionExpectationAsset[] submissionExpectationAssets);
}
}
The service
Ack! This solution is larger than I thought. I'm losing resolve on this write up but I will continue.
namespace Service.Integration
{
///
/// Manages interactions with Groupdocs.
///
public class GroupdocsConnectorService : IGroupdocsConnectorService
{
private readonly ILog _log; // Not relevant
private readonly IPlatformHttpClient _platformHttpClient; // Not relevant.
private readonly IPrincipalFactory _principalFactory; // Contains information about the logged in user.
private readonly IFile _file; // Asine wrapper around System.IO.File to make it "testable." http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf
private readonly IHttpClientWrapper _httpClientWrapper; // Ditto
private readonly IGroupdocsWrapper _groupdocsWrapper; // Ditto
private readonly IAzureWrapper _azureWrapper; // Ditto
private readonly IAssetService _assetService; // Ditto
public static string ContainerName
{
get { return "groupdocs"; } // Azure container name.
}
public GroupdocsConnectorService(ILog log, IPlatformHttpClient platformHttpClient, IPrincipalFactory principalFactory, IFile file,
IHttpClientWrapper httpClientWrapper, IGroupdocsWrapper groupdocsWrapper, IAzureWrapper azureWrapper, IAssetService assetService)
{
_log = log;
_platformHttpClient = platformHttpClient;
_principalFactory = principalFactory;
_file = file;
_httpClientWrapper = httpClientWrapper;
_groupdocsWrapper = groupdocsWrapper;
_azureWrapper = azureWrapper;
_assetService = assetService;
}
///
/// Downloads the asset from Azure to the Groupdocs network share and adds the current user as a collaborator.
///
/// The asset to prepare.
/// The Groupdocs user id (Which is a Guid fragment string).
public string PrepareDocument(Guid assetId) // assetId is the Groupdocs fileId
{
var principal = _principalFactory.Get(); // Get the logged in user.
var documentName = String.Format("{0}.pdf", assetId); // The fileId
// TODO: Groupdocs - 2014-07-02 - Delete this after successfully integrating with Azure
TEMP_DownloadAssetToFile(assetId); // This was a temp solution until we had Azure integration up. We never got that working so this function is still named TEMP_. I'm not changing it for this write up lest I miss a reference and cause confusion.
var webAnnotationService = _groupdocsWrapper.GetWebAnnotationService(); // Use ObjectFactory.GetInstance(); instead
var setCollaboratorResult = webAnnotationService.AddCollaborator(documentName, principal.Id, principal.DisplayName, "", null);
webAnnotationService.SetCollaboratorColor(documentName, principal.Id.ToString(), 16711680);
webAnnotationService.SetCollaboratorRights(documentName, principal.Id.ToString(),
AnnotationReviewerRights.CanAnnotate | AnnotationReviewerRights.CanDelete | AnnotationReviewerRights.CanRedact | AnnotationReviewerRights.CanView);
var reviewerInfo = setCollaboratorResult.Collaborators.First(ri => ri.PrimaryEmail.Equals(principal.Id.ToString()));
return reviewerInfo.Guid; // This is the id from the Groupdocs database. It's returned to the client so it can be passed to the iframe.
}
#region Save annotated document
///
/// 1. Generates the annotated PDFs.
/// 2. Saves the PDFs to the Groupdocs network share.
/// 3. Uploads the annotated PDF to the Asset service.
/// 4. Updates the grade.
///
/// Id of the student who is being graded.
/// Information about the grade that is being updated.
/// List of submission expectation Ids and their corresponding asset ids
public void SaveAnnotatedDocument(Guid sectionId, Guid studentUserId, Guid learningActivityId, Guid gradeId, UpdateGradeRequest updateGradeRequest, SubmissionExpectationAsset[] submissionExpectationAssets)
{
var generatedPDFAssetIds = new Dictionary();
foreach (var submissionExpectationAsset in submissionExpectationAssets)
{
var asset = _assetService.GetAsset(submissionExpectationAsset.AssetId); // asset.id is used as the fileId
if (asset.Type.Equals("File", StringComparison.OrdinalIgnoreCase)) // Not relevant
{
string tempAnnotatedPDFFilePath = GenerateAnnotatedPDF(submissionExpectationAsset.AssetId); // Generates the annotated PDF on the groupdocs DFS on Azure.
if (!string.IsNullOrWhiteSpace(tempAnnotatedPDFFilePath))
{
var uploadRequestInfo = _assetService.GetUploadRequestInfo(); // Not relevant.
using (var fileStream = (Stream)_file.Open(tempAnnotatedPDFFilePath, FileMode.Open))
{
_assetService.UploadToAzure(fileStream, uploadRequestInfo.UploadRequestUrl); // Uploads the file to Azure. Not relevant.
var newAsset = CreateNewAsset(asset, uploadRequestInfo.UploadRequestId.ToString()); // Not relevant.
var newAssetId = _assetService.PostAsset(newAsset); // Not relevant.
// ...store the returned asset's id so we can use it when updating the grade...
generatedPDFAssetIds.Add(submissionExpectationAsset, newAssetId); // Not relevant.
// ...and move the generated file to the Groupdocs file share.
SaveAnnotatedPDFToGroupdocsShare(newAssetId, tempAnnotatedPDFFilePath);
}
_file.Delete(tempAnnotatedPDFFilePath);
}
}
}
UpdateGrade(sectionId, studentUserId, learningActivityId, gradeId, updateGradeRequest, generatedPDFAssetIds); // Not relevant.
}
private string GenerateAnnotatedPDF(Guid assetId)
{
string tempFilePath = String.Empty;
var documentName = assetId.ToString() + ".pdf";
long sessionId = _groupdocsWrapper.GetSessionId(documentName);
if (sessionId > 0) // sessionId will only be > 0 if the teacher has looked at the document.
{
var coreAnnotationService = _groupdocsWrapper.GetCoreAnnotationService(); // Use ObjectFactory.GetInstance(); instead.
var sessionAnnotations = coreAnnotationService.GetSessionAnnotations(sessionId);
// We only generate the annotated document if the teacher actually made annotations.
if (sessionAnnotations.Length > 0)
{
tempFilePath = TEMP_DownloadAssetToFile(assetId);
string tempFileName = _groupdocsWrapper.ExportAnnotations(sessionId, documentName, DocumentType.Pdf, AnnotationMode.TrackChanges); // Use AnnotationsExporter.Perform(sessionId, documentName, outputType, mode); instead
// AnnotationsExporter.Perform is hard-coded to write the file to the user's temp directory.
tempFilePath = Path.Combine(Path.GetTempPath(), "Saaspose", tempFileName);
}
}
return tempFilePath;
}
// Entire method is not relevant.
private Web.Contract.Asset.Asset CreateNewAsset(Web.Contract.Asset.Asset asset, string uploadRequestId)
{
// Set the new asset's name...
var assetName = asset.Name;
var newAssetName = assetName.Substring(0, assetName.LastIndexOf(".")) + "_graded.pdf";
// ...and populate the new asset request object...
var newAsset = new Web.Contract.Asset.Asset();
newAsset.UploadRequestId = new Guid(uploadRequestId);
newAsset.Name = newAssetName;
newAsset.Type = asset.Type;
var metaDataJson = JObject.Parse(asset.MetaData);
metaDataJson["fileType"] = "pdf";
metaDataJson["fileName"] = newAssetName;
newAsset.MetaData = Regex.Replace(metaDataJson.ToString(), @"\s+", "");
return newAsset;
}
// Entire method is not relevant.
private void UpdateGrade(Guid sectionId, Guid studentUserId, Guid learningActivityId, Guid gradeId, UpdateGradeRequest updateGradeRequest, Dictionary generatedPDFAssetIds)
{
if (generatedPDFAssetIds.Count > 0)
{
updateGradeRequest.AnnotatedAssets = new List();
foreach (var generatedPDFAssetId in generatedPDFAssetIds)
{
updateGradeRequest.AnnotatedAssets.Add(new AnnotatedAssetRequest()
{
SubmissionExpectationAssetId = generatedPDFAssetId.Key.SubmissionExpectationAssetId,
AssetId = generatedPDFAssetId.Value
});
}
var updateGradeRequestJson = JObject.FromObject(updateGradeRequest);
// Update the final grade with the new asset IDs
//todo: Update this to use a repository
var annotatedAssetResult = _platformHttpClient.PutAsync(
String.Format("section/section/{0}/user/{1}/learningActivity/{2}/grade/{3}", sectionId, studentUserId, learningActivityId, gradeId),
new StringContent(updateGradeRequestJson.ToString(), Encoding.UTF8, "application/json")).Result;
if (!annotatedAssetResult.IsSuccessStatusCode)
{
throw new Exception(String.Format("Call to platform grade service (update grade) failed while generating annotated PDF: {0} ({1}). \n\r{2}",
(int)annotatedAssetResult.StatusCode, annotatedAssetResult.ReasonPhrase, annotatedAssetResult.Content.ReadAsStringAsync().Result));
}
}
}
// Entire method is not relevant.
private void SaveAnnotatedPDFToGroupdocsShare(Guid assetId, string tempFilePath)
{
var groupdocsFilePath = Path.Combine(GetGroupdocsPath(), assetId.ToString() + ".pdf");
if (!_file.Exists(groupdocsFilePath)) // Use File.Exists instead
{
_file.Copy(tempFilePath, groupdocsFilePath, true); // Use File.Copy instead
}
}
#endregion
public static string GetGroupdocsPath()
{
var path = ConfigurationManager.AppSettings["GroupdocsPath"]; // The path to the DFS is stored in the app.config.
if (path.StartsWith("~"))
{
return HttpContextFactory.Current.Server.MapPath(path);
}
return path;
}
// TODO: Groupdocs - 2014-07-02 - Delete this after successfully integrating with Azure
// I think entire method is not relevant.
private string TEMP_DownloadAssetToFile(Guid assetId)
{
string filePath = Path.Combine(GetGroupdocsPath(), assetId.ToString() + ".pdf");
if (!_file.Exists(filePath))
{
var httpResponse = _platformHttpClient.GetAsync(String.Format("Asset/Asset/{0}/ReadToken?format=PDF", assetId));
if (httpResponse == null)
{
throw new Exception("Error retrieving user from platform account service, response was null.");
}
var response = httpResponse.Result;
if (response.IsSuccessStatusCode)
{
var sourceBlob = _azureWrapper.GetCloudBlockBlob(new Uri(response.Content.ReadAsStringAsync().Result.Replace("\"", "")));
// Write it to the Groupdocs path
sourceBlob.DownloadToFile(filePath, FileMode.Create);
}
else
{
throw new ApplicationException(String.Format("Call to platform user service API failed: {0} ({1}). \n\r{2}",
(int)response.StatusCode, response.ReasonPhrase, response.Content.ReadAsStringAsync().Result));
}
}
return filePath;
}
}
}