Introduction
There are a few articles about the Background Intelligent Transfer Service or BITS here. I wanted to add something more. Using BITS with COM interop isn't really difficult, but I thought this would be a fun project to do anyway.
For a complete reference on BITS, do read all about it on MSDN first.
When I started, I had some goals in mind.
- Completely Forms Designer friendly that works as much like
ListView
and ListViewItem
as possible.
- Wrap all features of BITS through version 3.0.
- Easy to use and relatively idiot proof. Someone's always making a better idiot, but I try.
- Automatic handling of certain events. Fear not control freaks, all of the automatic handling can be shut off on a job by job basis.
- All exceptions coming from my code derive from a common base class. You'll only need one
catch
handler. If I didn't throw the exception, then InnerException
will contain the exception thrown by COM or the CLI.
Here's a simple example of creating a download job.
System.Net.BITS.Manager manager = new System.Net.BITS.Manager();
System.Net.BITS.Job job = new System.Net.BITS.Job("Simple Job");
job.Files.Add("http://www.someplace.com/download/file.zip", "c:\file.zip");
manager.Jobs.Add(job);
job.Resume();
That's pretty easy. You could get away with doing this little. BITS will take care of the download, and IBackgroundCopyJob.Complete()
will automatically be called when the job completes. If there's an error, IBackgroundCopyJob.Cancel()
will automatically be called.
What this example lacks is a way to know when the job finishes. You can do this in one of two ways, using polling or events. Polling works just fine, use a timer to check the job state, or if you need something synchronous a simple loop will work.
while (job.State != JobState.Cancelled &&
job.State != JobState.Acknowledged)
Sleep(1000);
Of course, polling is not the best approach. The job will eventually finish one way or another, but the loop above could take a while. Let's use events instead.
System.Net.BITS.Manager manager = new System.Net.BITS.Manager();
System.Net.BITS.Job job = new System.Net.BITS.Job("Simple Job");
manager.OnModfication += new
EventHandler<JobModificationEventArgs>(manager_OnModfication);
job.Files.Add("http://www.someplace.com/download/file.zip",
"c:\file.zip");
manager.Jobs.Add(job);
job.Resume();
}
private void manager_OnModfication(object sender,
JobModificationEventArgs e)
{
if (e.Job.State == JobState.Cancelled &&
e.Job.State == JobState.Acknowledged)
{
}
}
There's a lot more to BITS that what is in this example. Fortunately for me, it's documented by Microsoft already. I did add a few things, so I'll go over them. Not everything will be covered, just the important stuff.
General
Each version of BITS adds more features. Be sure to check what version of BITS was instantiated. An exception will be thrown if you try to use features from a higher version of BITS than what you have on your machine. You won't get the exception in the designer. You can set everything in the designer, you'll get the exception as soon as your code runs. Be careful not to set properties in the designer if you want to support older versions of BITS.
I didn't test everything! Sorry. I tested a lot of the functionality on both XP and Vista. I did not try an upload with reply job.
Not everything works as Microsoft has documented. When running on XP, if you set a command line notification, it will not run unless you set Job.EventsEnabled
to false
. Doing so means no events will fire for that job. You can still use polling. Command line notifications do work as documented on Vista. By the way, setting a command line notification in the OnTransferred
event doesn't work. You have to set the command line sooner.
The documentation on MSDN is not up to date, and some of the stuff added in BITS version 3.0 is not documented.
If you have the System.Net.Bits
assembly and the project that uses it in the same solution, Visual Studio can have problems in the designer. The sample project does this. I reference System.Net.BITS
as a file reference not as a project reference, and whenever I change something in System.Net.BITS
, I usually have to quit Visual Studio and rerun, otherwise the designer gets goofy.
The Manager Class
You will need to create an instance of Manager
. You can create more than one, but one is all you need. It wraps IBackgroundCopyManager
. It will try to instantiate the highest version of the BITS COM class. If BITS is not installed on your machine, you'll still be able to create a Manager
, the constructor won't throw an exception. This will let you use the Forms Designer even when BITS is unavailable.
The Manager
maintains the list of jobs. A job doesn't do anything unless it's in the Jobs
collection. Just like ListViewItem
, you can set its properties but its doesn't live until it's in a ListViewItemCollection
. The Jobs
collection is, by default, automatically populated with the jobs in IEnumBackgroundCopyJobs
belonging to the current user. You can shut that off if you wish, or get all jobs belonging to all users. The Manager
has an Update
method. You can use Update
to check IEnumBackgroundCopyJobs
for new jobs, or remove jobs BITS no longer maintains. I.e., when one of your jobs completes, if you call JobCollection.Update()
, your job will no longer be in the Jobs
collection.
Jobs can be added or removed from the collection at any time. When removed from the collection, the IBackgroundCopyJob
maintained by BITS still exists. If it was downloading before, it'll still be downloading. The job object is simply no longer connected to the IBackgroundCopyJob
. This also means you can quit your app and your jobs will still be there when you start up again.
The Manager
also has the job related events. Even though all events are connected to a job, it's much easier to connect to an event once than have to do it for each and every job. Not only that, BITS likes to send events as soon as possible, and often. By putting the events in the Manager
class, you won't miss an event.
BITS likes to send lots of events and on different threads. BITS doesn't queue up events for the same job. You can have multiple events pre-empting each other. I think that's annoying. So I wrapped all the events in a Mutex.
The Job Class
For the most part, this is just a wrapper around IBackgroundCopyJob
. Most things about a job are exposed as properties. Creating a job is not the same thing as creating the IBackgroundCopyJob
. In order to support the designer, all of the designer settable properties are cached in the job until you call a method to create the actual IBackgroundCopyJob
. I added a JobState
for this case, called JobState.Inactive
. When you construct the job, it will be in this state. If you call Job.Activate()
or Job.Resume()
, then the IBackgroundCopyJob
is created. Any properties set at design time are set on the IBackgroundCopyJob
. This is also when the files are added to the IBackgroundCopyJob
as well.
Jobs can be cloned. All the job properties are set on the clone. Even the credentials. This will only work if you set all the credentials in this job instance. There's no way to get them from the IBackgroundCopyJob
. So if your jobs collection was populated with an existing job, it won't have any credentials cached in it.
Jobs maintain a collection of files. While designing a job, you can add and removes files as you wish. Once the IBackgroundCopyJob
is created, you can no longer remove files. For some reason, BITS doesn't support it.
Some properties, like ProxySettings
, return a class. This is nice and handy, but there's a caveat. Don't go doing this.
job.ProxySettings.ProxyUsage = ProxyUsage.NoProxy;
It won't do anything. I could have made that work but decided against it. IBackgroundCopyJob.SetProxySettings()
expects three arguments. If something is wrong with one of them, an exception is thrown. So if you want to change the proxy settings, you have to change them all at once.
JobProxySettings settings = job.ProxySettings;
settings.ProxyUsage = ProxyUsage.NoProxy;
job.ProxySettings.ProxyUsage = settings;
Doing the above will result in a call to IBackgroundCopyJob.SetProxySettings()
.
References
History
- 09/04/2006: Posted to CodeProject.