Welcome to MSDN Blogs Sign in | Join | Help

Forms Attachments

In Groove Forms, you can add an Attachments field to your forms, and store any number of attachments in the field.  (You can define more than one attachments field in the tool, but each form can only include one attachments field).

Let's look at accessing these attachments with web services.

Here's the schema of a form with one attachments field:

<xs:complexType>
<xs:sequence>
<xs:element name=\"Attachments\" type=\"FileAttachments\" minOccurs=\"0\" />
<!-- ... the other form fields ... --!>
</xs:sequence>
</xs:complexType>

The FileAttachments type is also defined in the schema; you can see the specification in Forms2.xsd in the development kit.  It consists of three linked types:

  • An unbounded sequence of FileAttachment elements; each of which is...
  • A sequence of {ID, Created, CreatedBy, Modified, ModifiedBy, FullName, Type, Size, and Contents}, where Contents is...
  • A Base64 binary value.

When you read this structure into a DataSet, each of these types is represented as a separate table, with child relationships between the form, the FileAttachments, each FileAttachment, and its FileContents.

Convenient? Not without a small amount of code to wrap access to the attachments.  So let's write the code.

My model here is to build a helper class which you can construct from a DataRow of forms data.  This helper should provide nice easy methods to enumerate existing file attachments, and to create and delete attachments too.  So here we go:

public class GrvFormsRecord

{

private DataRow _row;

/// Construct a GrvFormsRecord from the DataRow representing the form data.

public GrvFormsRecord(DataRow dataRow)

{

_row = dataRow;

}

public bool SupportsAttachments

{

get

{

return (_row != null && _row.Table.ChildRelations.Count > 0);

}

}

private DataRow[] AttachmentRows

{

get

{

DataRow[] attachmentRows = new DataRow[0];

if (SupportsAttachments)

{

DataRelation relationForAttachProp = _row.Table.ChildRelations[0];

DataRow[] attachPropRows = _row.GetChildRows(relationForAttachProp);

if (attachPropRows.Length > 0)

{

DataRow attachPropRow = attachPropRows[0];

if (attachPropRow.Table.ChildRelations.Count > 0)

{

DataRelation relationForFileAttach = attachPropRow.Table.ChildRelations[0];

attachmentRows = attachPropRow.GetChildRows(relationForFileAttach);

}

}

}

return attachmentRows;

}

}

}

The SupportsAttachments method is simple.  If the row doesn't define any child relationships, then your form doesn't include any FileAttachment-type fields, so you can't attach files to it.

Then there's a (private) getter to find the actual rows representing each file attachment.  This finds the single child row in the FileAttachments table, which represents the FileAttachments data type; then returns an array of all the FileAttachment child rows.  Each of these rows has columns for the attachment's metadata: FullName, Size, and so on.

So far, so good.  Now it's easy to write this:

public int AttachmentCount

{

get

{

return AttachmentRows.Length;

}

}

Next, I'll create a separate class to represent the attachment itself.  Once we have such an object, the rest of my GrvFormsRecord class is easy too: provide an enumerator for attachments, and methods to create and delete attachments from the record.

public IEnumerable<GrvFormsRecordAttachment> Attachments

{

get

{

foreach (DataRow attachmentRow in AttachmentRows)

{

GrvFormsRecordAttachment att = new GrvFormsRecordAttachment(attachmentRow);

yield return att;

}

}

}

public GrvFormsRecordAttachment CreateAttachment(string name, byte[] content)

{

if (!SupportsAttachments)

{

throw new Exception("Record does not support attachments.");

}

return new GrvFormsRecordAttachment(_row, name, content);

}

public void DeleteAttachment(GrvFormsRecordAttachment att)

{

att.Delete();

}

And this makes the application-level code look really simple.  Something like this,

// Application code using GrvFormsRecord...

DataRow dr = formsDataTable.NewRow();

GrvFormsRecord rec = new GrvFormsRecord(dr);

if (rec.SupportsAttachments)

{

string fileName = // something.txt

byte[] fileContents = // the attachment contents

rec.CreateAttachment(fileName, fileContents);

}

formsDataTable.Rows.Add(dr);

My GrvFormsRecordAttachment class wraps the attachment-metadata row and the related FileContents row into a single object.  Here I've made the constructors "internal", since the only way I want to create one of these objects is via the GrvFormsRecord class above.  Without further explanation, then, here's the rest of the code.

/// <summary>

/// Helper class representing an attachment to a Forms record.

/// Don't construct this directly; use the GrvFormsRecord class methods.

/// </summary>

public class GrvFormsRecordAttachment

{

DataRow _attachmentRow;

DataRow _contentsRow;

// Construct from an existing row

internal GrvFormsRecordAttachment(DataRow attachmentRow)

{

_attachmentRow = attachmentRow;

DataRelation relationForContent = _attachmentRow.Table.ChildRelations[0];

DataRow[] contentRows = _attachmentRow.GetChildRows(relationForContent);

_contentsRow = contentRows[0];

}

// Construct a new attachment from name and content

internal GrvFormsRecordAttachment(DataRow recordRow, string name, byte[] content)

{

DataRelation relationForAttachProp = recordRow.Table.ChildRelations[0];

DataRow[] attachPropRows = recordRow.GetChildRows(relationForAttachProp);

DataRow attachPropRow;

if (attachPropRows.Length == 0)

{

DataTable propertiesTable = relationForAttachProp.ChildTable;

attachPropRow = propertiesTable.NewRow();

propertiesTable.Rows.Add(attachPropRow);

attachPropRow.SetParentRow(recordRow);

}

else

{

attachPropRow = attachPropRows[0];

}

// Create the row in the FileAttachment table

// Note: the attachment's name is the FullName field.

// in Groove V3.1 this was two properties, Name (filename with extension) and DisplayName (filename without extension)

DataRelation relationForFileAttach = attachPropRow.Table.ChildRelations[0];

DataTable fileAttachmentTable = relationForFileAttach.ChildTable;

_attachmentRow = fileAttachmentTable.NewRow();

_attachmentRow["FullName"] = name;

_attachmentRow["Type"] = "File";

_attachmentRow["Size"] = content.Length;

_attachmentRow.SetParentRow(attachPropRow);

fileAttachmentTable.Rows.Add(_attachmentRow);

// Create the row in the Contents table

// and set the binary contents of the attachment

DataRelation relationForContent = _attachmentRow.Table.ChildRelations[0];

DataTable contentsTable = relationForContent.ChildTable;

_contentsRow = contentsTable.NewRow();

_contentsRow["Base64"] = content;

_contentsRow.SetParentRow(_attachmentRow);

contentsTable.Rows.Add(_contentsRow);

}

// Delete this attachment.

internal void Delete()

{

_contentsRow.Delete();

_contentsRow = null;

_attachmentRow.Delete();

_attachmentRow = null;

}

/// <summary>

/// Filename of the attachment. Read-write.

/// </summary>

public string Name

{

get

{

return (string)_attachmentRow["FullName"];

}

set

{

_attachmentRow.BeginEdit();

_attachmentRow["FullName"] = value;

_attachmentRow.EndEdit();

_attachmentRow.AcceptChanges();

}

}

/// <summary>

/// Size of the attachment, in bytes. Read-only.

/// </summary>

public long Size

{

get

{

return (long)_attachmentRow["Size"];

}

}

/// <summary>

/// The attachment contents. Read-write.

/// </summary>

public byte[] Content

{

get

{

byte[] data = (byte[])_contentsRow["Base64"];

return data;

}

set

{

_attachmentRow.BeginEdit();

_attachmentRow["Size"] = value.Length;

_attachmentRow.EndEdit();

_attachmentRow.AcceptChanges();

_contentsRow.BeginEdit();

_contentsRow["Base64"] = value;

_contentsRow.EndEdit();

_contentsRow.AcceptChanges();

}

}

}

This pair of classes is simple and reusable enough to just drop in to any Forms-tool application projects, making forms attachments access a snap.

Published Wednesday, June 28, 2006 1:28 PM by hpyle

Comments

No Comments

Anonymous comments are disabled
 
Page view tracker