C
C#6d ago
felsokning

Populate `EnvelopeRecipientCollection` on `Recipients` Property of a `MailItem` in Transport Agent

I've written an Exchange Transport Agent in C#, using the common assemblies from Exchange 2019 CU14 (namely: Microsoft.Exchange.Data.Common.dll and Microsoft.Exchange.Data.Transport.dll). I, at first, had issues populating the MailItem object on the EndOfDataEventArgs delegate. I discovered that I could do this via reflection and now I'm trying to test a condition that the message is quarantined. In order for the message to be quarantined, the recipients - which is a List<EnvelopeRecipient> has to be populated and passed into the ReceiveMessageEventSource.Quarantine method. The issue that I'm running into is that the Recipients property on the MailItem only has a getter and is of type EnvelopeRecipientCollection. So, I try to setup a Mock<EnvelopeRecipientCollection> and am able to feed that as the SetupGet(x => x.Recipients).Returns(...); however, because it's a mock, it's an empty collection and the test is failing because the recipient collection is empty. Since the constructor for EnvelopeRecipientCollection is internal, I can't instantiate an instance of the object. It looks like EnvelopeRecipientCollection depends on a struct of IEnumerator<EnvelopeRecipient>, so trying to pass the List<EnvelopeRecipient>.GetEnumerator() as the EnvelopeRecipientCollection's enumerator doesn't work, either. Is there a way to successfully "fake" an object - even if it's a List<EnvelopeRecipient> - as an EnvelopeRecipientCollection and pass that into the SetupGet, so that the test doesn't continually fail with a NullReferenceException when trying to fetch the recipients for the ReceiveMessageEventSource.Quarantine method? Recipients Property (for 2010 - but hasn't changed in 2019): https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa579340(v=exchg.140) EnvelopeRecipientCollection: https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa564107(v=exchg.140)
11 Replies
Sossenbinder
Sossenbinder6d ago
Any chance you got a code snippet making the issue a bit clearer? When you say some of these objects only have private setters etc, can you backtrack to where the objects are instantiated? They'd probably receive that part as a constructor parameter I assume
felsokning
felsokningOP6d ago
The helper to translate the email to an email item also came from there.
felsokning
felsokningOP6d ago
This is the deconstructed view of the MailItem. As you can see, it only has a get for the Recipients property and the constructor is marked as internal. When Recipients is populated, I have to assume it's on the EndOfHeaders process in the Transport Pipeline, itself?
felsokning
felsokningOP6d ago
And here's the deconstructed view of EnvelopeRecipientCollection.
felsokning
felsokningOP6d ago
Sorry, forgot: SetPropertyValue is just a reflection extension.
[ExcludeFromCodeCoverage]
public static class Reflection
{
private static PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
PropertyInfo propInfo = null;
do
{
propInfo = type.GetProperty(propertyName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
type = type.BaseType;
}
while (propInfo == null && type != null);
return propInfo;
}

public static void SetPropertyValue(this object obj, string propertyName, object val)
{
if (obj == null)
throw new ArgumentNullException("obj");
Type objType = obj.GetType();
PropertyInfo propInfo = GetPropertyInfo(objType, propertyName);
if (propInfo == null)
throw new ArgumentOutOfRangeException("propertyName",
string.Format("Couldn't find property {0} in type {1}", propertyName, objType.FullName));
propInfo.SetValue(obj, val, null);
}
}
[ExcludeFromCodeCoverage]
public static class Reflection
{
private static PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
PropertyInfo propInfo = null;
do
{
propInfo = type.GetProperty(propertyName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
type = type.BaseType;
}
while (propInfo == null && type != null);
return propInfo;
}

public static void SetPropertyValue(this object obj, string propertyName, object val)
{
if (obj == null)
throw new ArgumentNullException("obj");
Type objType = obj.GetType();
PropertyInfo propInfo = GetPropertyInfo(objType, propertyName);
if (propInfo == null)
throw new ArgumentOutOfRangeException("propertyName",
string.Format("Couldn't find property {0} in type {1}", propertyName, objType.FullName));
propInfo.SetValue(obj, val, null);
}
}
`
Sehra
Sehra6d ago
might be enough with mockEnvelopeRecipientCollection.Setup(x => x.GetEnumerator()).Returns(recipients.GetEnumerator()); if all you depend on is IEnumerable<EnvelopeRecipient>
felsokning
felsokningOP6d ago
Gives the error about being different types: Argument 1: cannot convert from 'System.Collections.Generic.List<Microsoft.Exchange.Data.Transport.EnvelopeRecipient>.Enumerator' to 'Microsoft.Exchange.Data.Transport.EnvelopeRecipientCollection.Enumerator'
Tvde1
Tvde16d ago
Is there a reason you are trying to work with classes that are from an external library? I feel like you should not be unit testing those
felsokning
felsokningOP6d ago
I'm not testing classes from the external libraries but my own logic in the EndOfDataEventHandler delegate. Part of that logic is quarantining a message via ReceiveMessageEventSource.Quarantine, which requires an IEnumerable<EnvelopeRecipient> to be passed in. The only way I can derive that list is from the EnvelopeRecipientCollection from the MailItem, I believe. https://learn.microsoft.com/en-us/previous-versions/office/exchange-server-api/aa567154(v=exchg.150) https://learn.microsoft.com/en-us/previous-versions/office/exchange-server-api/aa564601(v=exchg.150)
Tvde1
Tvde13d ago
I would build a layer between this external API and your business logic. That way you can test your business logic without needing to construct external models

Did you find this page helpful?