IDocumentViewEndpoint

The IDocumentViewEndpoint interface is used by the IDocumentView implementations, to provide functionality which takes the output of the IDocumentView and returns it in a certain format, such as XML, JSON, RSS, iCal etc.  Each IDocumentViewPort implementation has an alias, which will become a part of the URL to call, a MimeType which dictates what kind of data it returns, and finally the GetData() method which takes in a DocumentQueryResult object and a context, and returns the data as a byte-array.

Interface declaration

public interface IDocumentViewEndpoint
{
	string MimeType { get; }

	string Alias { get; set; }

	Task<byte[]> GetData(DocumentQueryResult queryResult, DocumentViewContext context);
}

Example

This example shows the iCal document view endpoint, which allows you to create an endpoint which returns iCal data.  Along with the required interface members, it also defines a host of public properties which allow the user to configure what fields in the underlying document are used for the various fields of the iCal file.

The iCal document view endpoint uses the iCal.net library to generate the iCal file.

[DisplayName("iCalendar")]
[Description("Sets up an iCal view/feed for documents in this model")]
public class ICalDocumentViewEndpoint : IDocumentViewEndpoint
{
	private readonly IStringFormatService _stringFormatService;

	public string MimeType => "text/calendar";

	[EditorBrowsable]
	[DisplayName("Alias")]
	[Description("This is the name which will be a part of the URL which is used to call this view.  Avoid using special and/or language specific characters.")]
	public string Alias { get; set; }

	[EditorBrowsable]
	[DisplayName("Description field")]
	[Description("Choose the field in the model which will be used to populate the description field in the iCalendar")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string DescriptionField { get; set; }

	[EditorBrowsable]
	[DisplayName("Status field")]
	[Description("Choose the field in the model which will be used to populate the status field in the iCalendar")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string StatusField { get; set; }

	[EditorBrowsable]
	[DisplayName("Summary field")]
	[Description("Choose the field in the model which will be used to populate the summary field in the iCalendar")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string SummaryField { get; set; }

	[EditorBrowsable]
	[DisplayName("Location field")]
	[Description("Choose the field in the model which will be used to populate the location field in the iCalendar")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string LocationField { get; set; }

	[EditorBrowsable]
	[DisplayName("Created date field")]
	[Description("Choose the field in the model which will be used to populate the Created field in the iCalendar")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string CreatedField { get; set; }

	[EditorBrowsable]
	[DisplayName("Start date field")]
	[Description("Choose the field in the model which will be used to populate the Start field in the iCalendar")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string StartField { get; set; }

	[EditorBrowsable]
	[DisplayName("End date field")]
	[Description("Choose the field in the model which will be used to populate the End field in the iCalendar")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string EndField { get; set; }

	[EditorBrowsable]
	[DisplayName("Duration field")]
	[Description("Choose the field in the model which will be used to as the duration of the event - the end date will then be calculated from the Start date")]
	[EditorArgument("databinding", true)]
	[Category("Fields")]
	public string DurationField { get; set; }

	public ICalDocumentViewEndpoint(IStringFormatService stringFormatService)
	{
		_stringFormatService = stringFormatService;
	}

	public async Task<byte[]> GetData(DocumentQueryResult queryResult, DocumentViewContext context)
	{
		var calendar = new Calendar();

		foreach (var document in queryResult.Documents)
		{
			calendar.Events.Add(MapEvent(document));
		}

		var serializer = new CalendarSerializer();
		return Encoding.UTF8.GetBytes(serializer.SerializeToString(calendar));
	}

	private CalendarEvent MapEvent(Document document)
	{
		var values = GetFieldValues(document); 
		
		var result = new CalendarEvent();
		
		result.Description = _stringFormatService.FormatString(values, DescriptionField, System.Globalization.CultureInfo.InvariantCulture);
		result.Location = _stringFormatService.FormatString(values, LocationField, System.Globalization.CultureInfo.InvariantCulture);
		result.Status = _stringFormatService.FormatString(values, StatusField, System.Globalization.CultureInfo.InvariantCulture);
		result.Summary = _stringFormatService.FormatString(values, SummaryField, System.Globalization.CultureInfo.InvariantCulture);

		result.Uid = document.Id.ToString();

		if (DateTime.TryParse(_stringFormatService.FormatString(values, StartField, System.Globalization.CultureInfo.InvariantCulture), out DateTime startDate))
		{
			result.Start = new CalDateTime(startDate);
		}

		if (DateTime.TryParse(_stringFormatService.FormatString(values, EndField, System.Globalization.CultureInfo.InvariantCulture), out DateTime endDate))
		{
			result.End = new CalDateTime(endDate);
		}

		if (DateTime.TryParse(_stringFormatService.FormatString(values, CreatedField, System.Globalization.CultureInfo.InvariantCulture), out DateTime createDate))
		{
			result.Created = new CalDateTime(createDate);
		}

		if (!string.IsNullOrEmpty(DurationField))
		{
			var durationValue = _stringFormatService.FormatString(values, DurationField, System.Globalization.CultureInfo.InvariantCulture);
			if (int.TryParse(durationValue, out int duration))
			{
				result.Duration = new TimeSpan(0, duration, 0);
			}
		}

		return result;
	}

	private Dictionary<string, object> GetFieldValues(Document document)
	{
		var result = document.FieldValues;

		result.AddOrUpdate("_Id", document.Id);
		result.AddOrUpdate("_ModelId", document.ModelId);
		result.AddOrUpdate("_OwnerId", document.OwnerId);
		result.AddOrUpdate("_OwnerName", document.OwnerName);
		result.AddOrUpdate("_Created", document.Created);
		result.AddOrUpdate("_Modified", document.Modified);
		result.AddOrUpdate("_DisplayFrom", document.DisplayFrom);
		result.AddOrUpdate("_DisplayTo", document.DisplayTo);
		result.AddOrUpdate("_Disabled", document.Disabled);
		result.AddOrUpdate("_TagString", document.TagList.StringJoin(","));
		result.AddOrUpdate("_UID", document.UID);
		result.AddOrUpdate("_OrderIndex", document.UID);

		return result;
	}
}

Registration

This is how the implementation is registered into the DI container in Veva, this should be done in your module config file (The class which implements IModule).

public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
	services.AddNamed<IDocumentViewEndpoint, ICalDocumentViewEndpoint>();
}