27 May 2011

Generic Serialization

Another side post, while I also work on the MVC application.

One of the things I deal with on a regular basis is serialization of objects to and from files. Additionally, I see the questions come up on StackOverflow from time to time. So, to save myself some time and effort, and, hopefully, to help educate others, I wrote a generic serialization .dll.

The project is very small at the moment, though I may expand it as I decide to explore other types of Streams. Right now, it is strictly file serialization/deserialization. The over-arching idea is the ISerializer interface which defines two methods: Serialize and Deserialize. Then each class will implement that interface. The reason I used an interface is that I really do plan to expand this code and use it out in the wild. For that reason, I need to be able to serialize to whatever Stream type is appropriate at the moment, but I need to de-couple the serialization from the objects, as well as the presentation and business layers, as possible.

So, on to the code.

As I said, I started with a simple Interface: ISerializer:

namespace GenericSerializer
{
    public interface ISerializer
    {
        void Serialize<T>(T target);
        T Deserialize<T>();
    }
}

Notice the use of the T type designator. I want this completely generic so that ANY class which is decorated as [Serializable] will work with my code. This won't do any custom serialization, but I've never found that much need for it.

Now, my two classes: GenericXmlSerializer and GenericBinarySerializer. Again, both of these serialize only to files, at the moment. That will probably change in the future.

Here is the Xml Serializer:

public class GenericXmlSerializer : ISerializer
    {
        public string Path { getset; }

        public GenericXmlSerializer() { }
        public GenericXmlSerializer(string path)
        {
            Path = path;
        }

        public void Serialize<T>(T target)
        {
            using (FileStream fs = new FileStream(this.Path, FileMode.Create))
            {
                XmlSerializer xs = new XmlSerializer(typeof(T));
                xs.Serialize(fs, target);
            }
        }

        public T Deserialize<T>()
        {
            T result;
            using (FileStream fs = new FileStream(this.Path, FileMode.Open))
            {
                XmlSerializer xs = new XmlSerializer(typeof(T));
                result = (T)xs.Deserialize(fs);
            }
            return result;
        }
    }

I elected to have a Path property because I have to have an instance anyway. So we may as well ask for the path to which a file will be serialized at the time of class instantiation. Also note that the serialization code itself is very, very basic. I just use the default settings of XmlSerializer to write out the file. Deserialization is the same, I'm presuming the type T will be defined such that basic deserialization will be okay. If it isn't, of course, I'll have a problem. But this code wasn't designed to handle custom serialization anyway, so I'm not terribly concerned.

I made the same decisions with the Binary Serializer:

public class GenericBinarySerializer : ISerializer
    {
        public string Path { getset; }

        public GenericBinarySerializer() { }
        public GenericBinarySerializer(string path)
        {
            Path = path;
        }

        public void Serialize<T>(T target)
        {
            using (FileStream fs = new FileStream(this.Path, FileMode.Create))
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(fs, target);
            }
        }

        public T Deserialize<T>()
        {
            T result;
            using (FileStream fs = new FileStream(this.Path, FileMode.Open))
            {
                BinaryFormatter bf = new BinaryFormatter();
                result = (T)bf.Deserialize(fs);
            }
            return result;
        }
    }

Now, we have our serializers, but do they work? The only way to know is to set up another project which uses them. In this case, I'll use the same solution, but a new project. In the wild, of course, we'll be using different solutions with references to the .dll.

So, SampleSerializationProject: It has a Class: Employee, an Enum: WorkRole, and a simple console application that will put together a couple of files for us.

The Employee Class:

[Serializable]
    public class Employee
    {
        public int EmployeeId { getset; }
        public string LastName { getset; }
        public string FirstName { getset; }
          
        [XmlEnum]
        private WorkRole role;

        public WorkRole Role
        {
            get { return role; }
            set { role = value; }
        }
        public double Salary { getset; }
        public bool Exempt { getset; }

        public override string ToString()
        {
            return string.Format("Employee Id: {0} :: {1}, {2}", EmployeeId, LastName, FirstName);
        }
    }

Note that the WorkRole could not be represented as an automatically implemented property, I had to specify the backing field to use the [XmlEnum] attribute.

The WorkRole Enum:

   [Flags()]
    public enum WorkRole
    {
        Clerk,
        Laborer,
        Manager,
        Officer
    }

Here, note the [Flags()] attribute. This allows the binary formatter to serialize to and from the enum in the same way that [XmlEnum] allows the XmlSerializer to do so.

Finally, the program itself:

static void Main(string[] args)
        {
            var Employees = new List<Employee>
            {
                new Employee() {EmployeeId = 1, LastName = "Smith", FirstName = "John", Role = WorkRole.Clerk, Salary = 10.00, Exempt = false},
                new Employee() {EmployeeId = 2, LastName = "Doe", FirstName = "Jane", Role = WorkRole.Laborer, Salary = 15.00, Exempt = false},
                new Employee() {EmployeeId = 3, LastName = "Zachary", FirstName = "Marcus", Role  = WorkRole.Manager, Salary = 50000.00, Exempt = true},
                new Employee() {EmployeeId = 4, LastName = "Goodner", FirstName = "Allen", Role = WorkRole.Officer, Salary = 80000.00, Exempt =true}
            };

            GenericXmlSerializer gxs = new GenericXmlSerializer(@"C:\Testing\testXmlSerialization.xml");
            GenericBinarySerializer gbs = new GenericBinarySerializer(@"C:\Testing\testBinarySerialization.dat");

            gxs.Serialize<List<Employee>>(Employees);
            gbs.Serialize<List<Employee>>(Employees);

            Employees = null;
            List<Employee> Employees2;

            Employees = gxs.Deserialize<List<Employee>>();
            Employees2 = gbs.Deserialize<List<Employee>>();

            Console.WriteLine("Output from Xml Deserialization");
            Employees.ForEach(e => Console.WriteLine(e));
            Console.WriteLine();
            Console.WriteLine("Output from Binary Deserialization");
            Employees2.ForEach(e => Console.WriteLine(e));

            Console.ReadKey();
        }

Here you see I'm creating a List of Type: Employee. I then create two generic serializers (one for each type I built) and provide the path I where I want the files to go. I Serialize to disk, then I null out the first list and create a second- just to prove that both types worked. I deserialize from both files, and then write the output to the Console.

Please, feel free to copy this code to your heart's content. If anyone needs me to, I'll happily post the compiled .dll on Codeplex.

26 May 2011

MVC Issue Tracker: Part 2

Apologies for the delay: But, I’m back in business now, so we’ll pick up where we left off…
Before we can begin with our Controllers, we need to finish one part of the data model: the Database Context.
Entity Framework makes this easy for us, in MVC3.  First, we add a new connection string
<add name="IssueTrackerDbContext"
         connectionString="Data Source=|DataDirectory|IssueTrackerDb.sdf"
         providerName="System.Data.SqlServerCe.4.0"/>

Then, we add a new folder under our Models folder.  I just called it “Data.”  To this folder, we’ll add two classes: IssueTrackerDbContext is our context class.
public class IssueTrackerDbContext : DbContext
    {
        public DbSet<Issue> Issues { get; set; }
        public DbSet<WorkNote> WorkNotes { get; set; }
        public DbSet<User> Users { get; set; }

        protected override void OnModelCreating
            (DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions
               .Remove<PluralizingTableNameConvention>();
        }
    }

This just sets up our entities for our Controllers.  Now, so that we’ll have data to play with as we make other changes, we add the IssueTrackerInitializer.
protected override void Seed(IssueTrackerDbContext context)
{
var Users = new List<User>
{
new User() 
{
UserName = "Agoodner", 
LastName = "Goodner", 
FirstName = "Allen", 
Password = "dontdothis", 
Role = Role.Administrator
}
};
 
Users.ForEach(u => context.Users.Add(u));
context.SaveChanges();
 
var issues = new List<Issue>
{
new Issue() 
{ 
Title = "Sample", 
Description = "A Samle issue.", 
CreatedBy = 1, 
CreatedDate = DateTime.Now, 
AssignedTo = 1, 
AssignedUser = Users[0] 
}
};
 
issues.ForEach(i => context.Issues.Add(i));
context.SaveChanges();
 
var WorkNotes = new List<WorkNote>
{
new WorkNote() 
{ 
IssueId = 1, 
UserId = 1,
Detail = "A Sample Note on a sample issue.", 
LoggedDate = DateTime.Now, 
AttachedIssue = issues[0]
},
new WorkNote() 
{ 
IssueId = 1, 
UserId = 1,
Detail = "Another Sample Note on a sample issue", 
LoggedDate = DateTime.Now, AttachedIssue = issues[0]
}
};
 
WorkNotes.ForEach(w => context.WorkNotes.Add(w));
 
var wnIssue = context.Issues.Where(i => i.IssueId == 1).Single();
 
WorkNotes.ForEach(w => wnIssue.WorkNotes.Add(w));
 
context.SaveChanges();
}

This will allow us to have some sample data when we first initialize  the application.
Now we can begin on our controllers.  We’ll start with an Issue Controller.  Whereas I skipped [Class]Model naming convention with the Models, I’ll leave the ‘Controler’ portion on our controllers.

CropperCapture[2]
For now, we’ll leave the Controller as we find it, but we will update the views.  First, the Index:
Most of our changes are cosmetic: Thus:
@model IEnumerable<MvcIssueTracker.Models.Issue>

@{
    ViewBag.Title = "Index";
}

<h2>Open Issues</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            Title
        </th>
        <th>
            Detailed Description
        </th>
        <th>
            Created By
        </th>
        <th>
            Created Date
        </th>
        <th>
            Assigned To
        </th>
        <th>
            Last Updated Date
        </th>
        <th>
            Closed By
        </th>
        <th>
            Closed Date
        </th>
        <th></th>
    </tr>
We changed the <h2> heading to “Open Issues” and we modified the column header names so they look a little more friendly.
The only thing of substance we did was to make the view aware of if an issue is closed or not:
@foreach (var item in Model) {
    if (item.ClosedDate == null)
    { 
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.Title)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Description)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.CreatedBy)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.CreatedDate)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.AssignedTo)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.LastUpdatedDate)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ClosedBy)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.ClosedDate)
        </td>
        <td>
            @Html.ActionLink("Edit", "Edit", new { id = item.IssueId }) |
            @Html.ActionLink("Details", "Details", new { id = item.IssueId }) |
            @Html.ActionLink("Delete", "Delete", new { id = item.IssueId })
        </td>
    </tr>
    }
Now, with the controller and view in place, we run the app and navigate to /Issue…
CropperCapture[3]
Similar work to the User Controller generates:
CropperCapture[4]
And with the WorkNote Controller produces:
CropperCapture[5]
We’ll stop there, for now.  Between this post and next, I’ll clean up those views, and hook up the Issue to the WorkNotes…

17 May 2011

Coding 201 – Separating your Code Layers

Above the Post Edit: In response to a request, the source code is available on CodePlex @ http://movielibrarydemo.codeplex.com/

Original Post:
One thing that has come to annoy me recently is the fact that, despite how much we talk about it, we never show new developers how actually to go about separating their code tiers, or layers, from each other. Then they get into some jam where good, object-oriented code would help, and they completely have to refactor.
Something like this came up today on StackOverflow.com (where I ask/answer as AllenG) when someone was building a simple library app to sort and store movies.

As soon as I heard this, my brain said to me, "Self," it said, "If I were doing this, I'd have an object layer with a Movie object and a Library Object, and they'd both be Serializable. Then I could just call an XmlSerializer and let it do the work." Which is, more or less, what I said. Unfortunately, the questioner did not understand how to go about making that happen. Well, out of a sense of fun, I had thrown the same project together anyway, and was going to post something about it (Don't worry, my next installment for my MVC3 series is slated for Thursday, unless something comes up). With his questions, I decided to go a little more into detail.

So, first, even for small projects, I believe that code should be broken into all three tiers: data, logic, UI. In very small projects, you could just manage it with folders, but I tend to use separate projects within a solution just to maintain the habit.

So, I created a solution (sorry, no screen caps- I'm on a different computer at the moment) which I just called MyMovieLibrary. The first project was a Class Library. I used it to create the following classes:
  1. Movie : Serializable
    1. Title
    2. Genre (an enum) //I had to make this an old style field/property to get it serialize with the XmlEnum attribute
    3. MPAA Rating
    4. Running Time
    5. Personal Rating
  2. Library : Serializable
    1. List<Movie>
  3. LibraryManager
    1. Library
    2. Methods to use the Library's List<Movie>.
Then I created the Genre enum.

If I were building it for real, I'd probably move most of the code from LibraryManager just into library, but there would be enough that I don't think the Library should know about that I'd still have all three classes.

Once I had my classes, I created a new project in the same solution that I called WinMovieLibrary. It referenced my class library project, and was a simple form.

The big thing that the OP needed was how to make the list-view work. So here that is, in all its glory (again, sorry for the formatting- I should be back on my main machine for the next MVC post…)

public Form1()
        {
            InitializeComponent();
            manager = new LibraryManager();
            if (File.Exists(ConfigurationManager.AppSettings["SaveLocation"]))
            {
                manager.Load();
            }
            else manager.Library = new Library() { Movies = new List<Movie>() };
            this.movieList.DataSource = manager.Library.Movies;
        }

As you can see, this is the Form initializer. You see that it calls an already saved file (if it exists) and loads the records into the library. Then, even if there are no movies, yet, it sets the DataSource of the ListBox to manager.Library.Movies.

When I update the library (say, by adding or deleting a movie), I then call this code:

        private void UpdateMovieListBox()
        {
            this.SuspendLayout();
            movieList.Invalidate();
            movieList.DataSource = null;
            movieList.DataSource = manager.Library.Movies;
            this.Update();
        }

This suspends the layout of the form, invalidates the movieList control, and then forces the DataSource to update. Finally, I allow the form to update. Which then gets me whatever the current version of the list is.
For serialization (where this started), as I mentioned, I simply marked up the Library and Movie classes as [Serializable], and my LibraryManager actually serializes them with this code (Save first, then Load):

        public void Save()
        {
            using (XmlWriter xWriter = XmlWriter.Create(ConfigurationManager.AppSettings["SaveLocation"]))
            {
                XmlSerializer xSer = new XmlSerializer(typeof(Library));
                xSer.Serialize(xWriter, Library);
            }
        }

        public void Load()
        {
            using (XmlReader xReader = XmlReader.Create(ConfigurationManager.AppSettings["SaveLocation"]))
            {
                XmlSerializer xSer = new XmlSerializer(typeof(Library));
                Library = (Library)xSer.Deserialize(xReader);
            }
        }

Now, back to my original point. Something I've noticed (and I've noticed it in everything from instructional books to tutorials online, to college level classes) is that we do not teach new developers this simple, foundational stuff. Had the OP known, from the beginning, that Movie should have been a class, and that his ListBox should just attach to a list for a DataSource, then his confusion over Serialization would not have required (probably) a question on SO followed by an exchange of comments. Even if he'd had to ask his question, as soon as someone said "Make your object serializable, then call an XmlSerializer" he'd have been all set.

So, for those of you who teach others how to write code, please incorporate this fundamental into your training material. It's not that hard to do, and it can save someone much time when they find they need to add functionality they hadn't thought of.

15 May 2011

Issue Tracker: Setting Up

To set up the project, I simply selected a New Project, selected ASP MVC 3 from the Web templates, renamed, and clicked OK.  That brought up the ASP MVC3 Dialog, where I selected an Internet Application, Razor view syntax, and did not choose to enable HTML 5 semantics.

That got me this:

CropperCapture[1]

A basic Website Set up.  If I run it, it says, essentially, “Hello World.”

Right-click the solution and select “Add Solution to Source Control” and it’s even on CodePlex for me.

That done, it’s time to move onto the models.  As I said before, we’ll have 3 table entities- Issues, Work Notes, and Users, and an enum value for Role.  So, first off, an Issue class…

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.DataAnnotations;
   4:  using System.Data.Entity;
   5:  using System.Linq;
   6:  using System.Web;
   7:   
   8:  namespace MvcIssueTracker.Models
   9:  {
  10:      public class Issue
  11:      {
  12:          public int IssueId { get; set; }
  13:   
  14:          [Required(ErrorMessage="A Title is required.")]
  15:          [StringLength(100, MinimumLength=15)]
  16:          public string Title { get; set; }
  17:   
  18:          [Required(ErrorMessage="A Detailed Description is required.")]
  19:          public string Description { get; set; }
  20:   
  21:          public int CreatedBy { get; set; }
  22:          public DateTime CreatedDate { get; set; }
  23:   
  24:          public int AssignedTo { get; set; }
  25:          public DateTime? LastUpdatedDate { get; set; }
  26:   
  27:          public int ClosedBy { get; set; }
  28:          public DateTime? ClosedDate { get; set; }
  29:   
  30:          public virtual User AssignedUser { get; set; }
  31:          public virtual ICollection<WorkNote> WorkNotes { get; set; }
  32:      }
  33:  }

Note that I dispensed with the convention of calling it “IssueModel.”  It’s in the Model folder (and, therefore, the MvcIssueTracker.Models namespace).  I went ahead and added my data notations as well, since I’ll need them eventually anyway.

One down, two (and a half?) to go.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Web;
   5:  using System.ComponentModel.DataAnnotations;
   6:   
   7:  namespace MvcIssueTracker.Models
   8:  {
   9:      public class WorkNote
  10:      {
  11:          public int WorkNoteId { get; set; }
  12:   
  13:          [Required(ErrorMessage = "Details are required.")]
  14:          public string Detail { get; set; }
  15:          public DateTime LoggedDate { get; set; }
  16:   
  17:          public int IssueId { get; set; }
  18:   
  19:          public virtual Issue AttachedIssue { get; set; }
  20:      }
  21:  }

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.DataAnnotations;
   4:  using System.Data.Entity;
   5:  using System.Linq;
   6:  using System.Web;
   7:   
   8:  namespace MvcIssueTracker.Models
   9:  {
  10:      public class User
  11:      {
  12:          public int UserId { get; set; }
  13:   
  14:          [Required(ErrorMessage = "UserName is required.")]
  15:          public string UserName { get; set; }
  16:   
  17:          public string LastName { get; set; }
  18:          public string FirstName { get; set; }
  19:          public string Location { get; set; }
  20:          public string Password { get; set; }
  21:          public Role Role { get; set; }
  22:   
  23:          public virtual ICollection<Issue> AssignedIssues { get; set; }
  24:      }
  25:  }

And, finally, the Roles enum:

   1:  namespace MvcIssueTracker.Models
   2:  {
   3:      public enum Role
   4:      {
   5:          User, //The base user- able only to create issues and add worknotes
   6:          Developer, //User functionality + close issues
   7:          Manager, //Same functionality as Developer- listed differently for audit type purposes
   8:          Administrator, //Developer + Delete issues
   9:      }
  10:  }

So, a quick Save All, and a Build to make sure nothing obvious is immediately broken… And we’re done.  A set of classes for our data model. 

In the next installment, we’ll look at getting the DataContext set up, and begin building controllers and views.

13 May 2011

ASP.NET MVC: A Journey

Hello, Gentle Readers.

I'm going to try something new: a journey on which you're allowed to ride along- and encouraged to back-seat drive.

As a developer, it pays to keep my skills up-to-date; this is something my current support posting makes more difficult than it might otherwise be. So, I decided that I should add some actual web development to my repertoire, and share the journey with you.

I'll be using ASP.NET MVC3, with its Code First abilities, to design a very light-weight, overly simplified Issue Tracking system. This is enough of a departure from the MV3 Tutorials that exist through Microsoft's site to show that I actually understand what I'm doing, and close enough to what they've done that I can crib from their notes when necessary.

I'll be hosting the project (as I build it) on CodePlex as MvcIssueTrackerDemo.codeplex.com. Go there for the latest code, and feel free to post recommendations/bugs/comments both at CodePlex and here in my comments.

Now, the best journeys begin with some idea of the destination, and the most successful have some idea of the stops along the way. So, to do our best to ensure a successful demonstration, we'll take the time to define what we're doing.

The Destination: A functional, if overly simplified and not-at-all secure Issue Tracker. It should be able to accept new issues, close issues, add work logs, and provide user maintenance and some basic statistics.
So, first, we'll design the Entity model, then we'll go about tackling each of the Entities to get them to behave like we expect.

The expected steps:
  1. Object Model
  2. Set up the Project
  3. Define the Models in code
  4. Create both Controllers and Views for each model- one at a time
  5. Prove it works
  6. Over view of the process.
So, in this, the first post, it only makes sense to discuss the object model.

I see the three following "entities" and one enum: Issues, Work Notes, and Users round out our entities, and we'll add a Role enum to enable some role-based security along the way.

Our Issue Object will include:
  • IssueId
  • Title
  • Detailed Description (I'll just call it 'Description')
  • CreatedBy (the User Id of the user who logged the issue)
  • CreatedDate
  • AssignedTo (the User Id of the user to whom the issue is assigned)
  • LastActivity (a DateTime)
  • ClosedBy (the User Id of the user who closed the issue)
  • ClosedDate
  • A virtual User for the Assigned User
  • A virtual ICollection<WorkNote> for the work logs.
Our WorkNote object will be very simple:
  • WorkNoteId
  • Details
  • UserId
  • DateTimeStamp
Users will have:
  • UserId
  • UserName
  • LastName
  • FirstName
  • Location (A vague term- I'll probably use it a Cube/Office number or something)
  • Password (In a real application, this would be a password salt and hash)
  • Role
  • A virtual ICollection<Issue> for the issues assigned to the User.

In the next post, we'll go through setting up the project, and defining the models.

04 May 2011

Negative Confirmation: Show Me the Code

I’ve blogged the last couple of days about Negative Confirmation as a Sender of data. That is, I believe that the person or organization who sends a file should have some process whereby files expected to be sent are flagged when they fail to go.

To that end, I’ve mocked up a little something that will do just that. First, here’s an overview

1) An Interface ILogReader with a single (overloaded) method: ReadLog which accepts either a Stream or a string. This is to enable the reading of virtually any kind of data. It would probably need to be modified to read a DB, but since this was more in the nature of a Proof-of-Concept, I’m not too concerned about that.

2) A Class DelimitedLogReader which Implements ILogReader- for this PoC, I’m just reading a pipe-delimited file of my own design.

3) A Class LogEntry to define the structure of log entries. In real code, this should probably be a struct, now that I think about it…

4) Classes for handling the Scheduling- these need some work. At the moment, I have a class for the ScheduleConfigurationSettings (pulling from app.config- I tried some custom settings, but that wasn’t working and wasn’t worth pursuing for a PoC). I have a ScheduleMarshaller- which grabs the actual schedule data from app.config. Finally, I have a ScheduleResolver- the real meat of the solution- which compares the schedule as provided by the ScheduleMarshaller to the logged files provided by the DelimitedLogReader.

So this works like this: The application instantiates DelimitedLogReader and grabs the log entries from a file specified in app.config. The application then instantiates the ScheduleResolver which instantiates the ScheduleMarshaller to get whatever files were scheduled to be sent the previous day. The Resolver then compares the scheduled files to the log entries and provides a list of the expected files which did not go.

Here is ILogReader in its entirety

clip_image001

DelimitedLogReader implements ILogReader- you could do this any number of ways, here’s mine:

clip_image002

The ReadLog(string path) method just returns a call to ReadLog(Stream stream) by instantiating a FileStream based on the path provided.

Things get a little more interesting with the ScheduleMarshaller

clip_image003

The call to “translateToDayOfWeek” takes a formatted string from the ScheduleConfiguration object and then translates that as a DateTime.DayOfWeek. “translateToDate” takes the expected day and turns it into an actual DateTime object.

The Resolver then does the actual work:

clip_image004

With the list of strings “fileInError” you can then handle them however you want. In my case- for my PoC only, I just did a simple console application:

clip_image005

And there you have it- a more-or-less functioning Negative Confirmation process which will alert me as the Sender when I have failed to send a file.

In a real-ish scenario, I would add a module to the end of any process that sends a mission-critical file to write to the log the name of the file and the DateTime when it was sent. My Resolver would then run once a day- late enough that anything that hadn’t gone would be in error- and alert me (probably via email) of any files that didn’t go. Better- it would not send an empty email if there were no results- it would just quietly finish and wait for tomorrow’s run.