2
votes

I'm trying to write a web service that queries Exchange using the EWS API (2.2). I can get the data using Console.Write but I want to provide the data to users of my SOAP web service.

I'm using the following code:

public class GetAppointments
{
    public static List<Appointment> GetAppointment()
    {
        ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
        service.UseDefaultCredentials = true;
        //service.Credentials = new WebCredentials("username", "password", "domain");
        service.TraceEnabled = true;
        service.TraceFlags = TraceFlags.All;
        service.AutodiscoverUrl("[email protected]", RedirectionUrlValidationCallback);
        //service.ImpersonatedUserId = new ImpersonatedUserID(ConnectingIdType.SmtpAddres, "user@domain");

        // Initialize values for the start and end times, and the number of appointments to retrieve.

        DateTime startDate = DateTime.Now;
        DateTime endDate = startDate.AddDays(30);
        //const int NUM_APPTS = 5;

        // Initialize the calendar folder object with only the folder ID. 
        CalendarFolder calendar = CalendarFolder.Bind(service, WellKnownFolderName.Calendar, new PropertySet());

        // Set the start and end time and number of appointments to retrieve.

        // CalendarView cView = new CalendarView(startDate, endDate, NUM_APPTS);

        ItemView view = new ItemView(100);
        // Set the mailbox to query
        String MailBoxToAccess = "[email protected]";
        FolderId CalendarFolderId = new FolderId(WellKnownFolderName.Calendar, MailBoxToAccess);

        // Limit the properties returned to the appointment's subject, start time, and end time.
        view.PropertySet = new PropertySet(AppointmentSchema.Subject, AppointmentSchema.Start, AppointmentSchema.End);

        // Retrieve a collection of appointments by using the calendar view.

        //FindItemsResults<Appointment> appointments = service.FindAppointments(CalendarFolderId, cView);

        SearchFilter subjectFilter = new SearchFilter.IsEqualTo(AppointmentSchema.Subject, "TEST");

        FindItemsResults<Item> appointments = service.FindItems(CalendarFolderId, subjectFilter, view);

        List<Appointment> AppointmentResult = new List<Appointment>();
        if (appointments.Items.Count > 0)
        {
            foreach (Appointment a in appointments)
            {
                var ApptResult = new Appointment(service)
                    {
                        Subject = a.Subject,
                        Start = a.Start,
                        End = a.End,
                    };

                AppointmentResult.Add(ApptResult);
            }
        }
        else
        {
            return null;
        }

        return AppointmentResult;
    }

    private static bool RedirectionUrlValidationCallback(string redirectionUrl)
    {
        // The default for the validation callback is to reject the URL.
        bool result = false;

        Uri redirectionUri = new Uri(redirectionUrl);

        // Validate the contents of the redirection URL. In this simple validation
        // callback, the redirection URL is considered valid if it is using HTTPS
        // to encrypt the authentication credentials. 
        if (redirectionUri.Scheme == "https")
        {
            result = true;
        }
        return result;
    }
}

And I get the following errors when I try to run it:

*Microsoft.Exchange.WebServices.Data.Appointment cannot be serialized because it does not have a parameterless constructor.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: Microsoft.Exchange.WebServices.Data.Appointment cannot be serialized because it does not have a parameterless constructor.

Source Error: An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace: 
[InvalidOperationException: Microsoft.Exchange.WebServices.Data.Appointment cannot be serialized because it does not have a parameterless constructor.]
   System.Xml.Serialization.TypeDesc.CheckSupported() +5651485
   System.Xml.Serialization.TypeDesc.CheckSupported() +45
   System.Xml.Serialization.TypeScope.GetTypeDesc(Type type, MemberInfo source, Boolean directReference, Boolean throwOnError) +172
   System.Xml.Serialization.XmlReflectionImporter.ImportMemberMapping(XmlReflectionMember xmlReflectionMember, String ns, XmlReflectionMember[] xmlReflectionMembers, Boolean rpc, Boolean openModel, RecursionLimiter limiter) +73
   System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(XmlReflectionMember[] xmlReflectionMembers, String ns, Boolean hasWrapperElement, Boolean rpc, Boolean openModel, RecursionLimiter limiter) +286
[InvalidOperationException: There was an error reflecting 'GetAppointmentResult'.]
   System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(XmlReflectionMember[] xmlReflectionMembers, String ns, Boolean hasWrapperElement, Boolean rpc, Boolean openModel, RecursionLimiter limiter) +899
   System.Xml.Serialization.XmlReflectionImporter.ImportMembersMapping(String elementName, String ns, XmlReflectionMember[] members, Boolean hasWrapperElement, Boolean rpc, Boolean openModel, XmlMappingAccess access) +133
   System.Web.Services.Protocols.SoapReflector.ImportMembersMapping(XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, Boolean serviceDefaultIsEncoded, Boolean rpc, SoapBindingUse use, SoapParameterStyle paramStyle, String elementName, String elementNamespace, Boolean nsIsDefault, XmlReflectionMember[] members, Boolean validate, Boolean openModel, String key, Boolean writeAccess) +233
   System.Web.Services.Protocols.SoapReflector.ReflectMethod(LogicalMethodInfo methodInfo, Boolean client, XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, String defaultNs) +2862
[InvalidOperationException: Method WebService.GetAppointment can not be reflected.]
   System.Web.Services.Protocols.SoapReflector.ReflectMethod(LogicalMethodInfo methodInfo, Boolean client, XmlReflectionImporter xmlImporter, SoapReflectionImporter soapImporter, String defaultNs) +6262
   System.Web.Services.Description.SoapProtocolReflector.ReflectMethod() +137
   System.Web.Services.Description.ProtocolReflector.ReflectBinding(ReflectedBinding reflectedBinding) +1577
   System.Web.Services.Description.ProtocolReflector.Reflect() +641
   System.Web.Services.Description.ServiceDescriptionReflector.ReflectInternal(ProtocolReflector[] reflectors) +559
   System.Web.Services.Description.ServiceDescriptionReflector.Reflect(Type type, String url) +109
   System.Web.Services.Protocols.DocumentationServerType..ctor(Type type, String uri, Boolean excludeSchemeHostPortFromCachingKey) +230
   System.Web.Services.Protocols.DocumentationServerProtocol.Initialize() +472
   System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing) +122
[InvalidOperationException: Unable to handle request.]
   System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing) +325
   System.Web.Services.Protocols.WebServiceHandlerFactory.CoreGetHandler(Type type, HttpContext context, HttpRequest request, HttpResponse response) +171
[InvalidOperationException: Failed to handle request.]
   System.Web.Services.Protocols.WebServiceHandlerFactory.CoreGetHandler(Type type, HttpContext context, HttpRequest request, HttpResponse response) +346
   System.Web.Services.Protocols.WebServiceHandlerFactory.GetHandler(HttpContext context, String verb, String url, String filePath) +209
   System.Web.Script.Services.ScriptHandlerFactory.GetHandler(HttpContext context, String requestType, String url, String pathTranslated) +47
   System.Web.HttpApplication.MapHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, Boolean useAppConfig) +226
   System.Web.MapHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +145
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155*

I'm not sure what I need to do to overcome this or if I'm even tackling this the right way. I'm open to suggestions.

1

1 Answers

4
votes

Sending a third party object over an API is not always a good idea and in this case it certainly looks that way. You need to consider that the consumer of your API needs to reconstruct the class at the other end and if it doesn't have a parameterless constructor, that's not possible.

Instead it's preferable to create your own model and send the values you require from it instead. For example, lets assume the Appointment class looks like this (I know it doesn't, but it's works as an example):

public class Appointment
{
    public string Title { get; set; }
    public string Body { get; set; }
    public DateTime MeetingTime { get; set; }
}

Then create your own class that mimics Appointment:

public class AppointmentModel
{
    public string Title { get; set; }
    public string Body { get; set; }
    public DateTime MeetingTime { get; set; }
}

Now instead of returning Appointment, return your own model:

public static List<Appointment> GetAppointment()
{
    //snip
    List<Appointment> appointments = GetAllAppointments()

    //Use some Linq to project into your own model
    return appointments
        .Select(a => new AppointmentModel
        {
            Title = a.Title,
            Body = a.Body,
            MeetingTime = a.MeetingTime
        })
        .ToList();
}