Introduction
Cofe3 is a set of libraries that enables developers to build their home cloud services for their specific kind of entries, which enables
the user to host them on their own computer, and using it to serve their own thin client computer like smart phone and tablet via
the web browser or apps. This library is released under the LGPL license.
Index
This article is mainly written for describing the internal workings for Cofe3.
To learn about how to use Cofe3, please read
Part I instead.
Part II
Features
Because I've been coding this for a while, I have no idea what others offer, this is a list of properties that I found interesting:
Not tied to file system
After so much work to make the properties cacheable in the database, accessible using web services, why limit it to only file system? That was a question I asked when the previous version of Cofe
was mostly completed. So version 2 was scrapped and version 3 was rewritten. The result, Cofe.IO is separate implementation. Cofe3 can have multiple implementations at a time, all implementations can participate
in path parsing, properties caching, and web exposing features like Cofe.IO.
Multiple source of properties
Just like FileSystemInfo
in the .NET Framework, Cofe.IO provides properties related to I/O, for example, file size, stream, etc. But that
is not sufficient because this is a general purpose library. So, we can define
a secondary property provider to supply additional properties, like md5 of a file, image size, etc.
Entry implements multiple interfaces
One entry (ICofeSystemInfo) can support different interfaces, for examples, a zip archive is both IFileInfo and IDirectoryInfo, while a text file is just IFileInfo. This is definable in external projects, e.g. a jpg image is IFileInfo in Cofe.IO and IImageInfo in Cofe.Media.
Thanks to EFCodeFirst, properties in implementer of these interfaces can be written to database directly (e.g. dbo.FileInfo table),
Most properties are cachable and searchable
The reason for caching entries in database, beside improving listing speed, it allows one to search for entry using filter string. Cofe.Data added the tags and description property which makes personalization possible, and they are searchable too: e.g.
root:{pictures} filetype:jpg OR filetype:png subdir:true tags:cofe
Two interfaces - ICofeSystemInfo and RESTApi
Cofe's property based system enable the properties accessible locally (through ICofeSystemInfo) or via the web. The webapi returns Json which is suitable for consumption by the included javascript library (written with CoffeeScript, JQuery and Spine). Although I used the work "properties", it actually means both static values and runnable actions (in Actions, Funcs or Tasks), so one can CRUD entry and its resources, Executing an action using WebAPI, provided they are marked executable in web and sufficient permission.
Cofe3.Core
Property
Every piece of data, every runnable method in an entry (ICofeSystemInfo and it's sub-interfaces) are property, for examples, we have CofeProperties.ParseName, we have CofeStreamProperties.OpenReadStreamAsync.
A group of related properties are reside in same enum, each property have an unique number, which can be lookup or reverse lookup using CofeServices.PropertyDictionary.
One major reason of declaring them as enum is that I can define declarative attributes on them, which allows specifing output types (PRopertyType, Action, ListAction), how to obtain the value (GetFirst, HandleOnce, AggregateResult), additional check to output (FileSise, BitmapSize), alternative name (Alias), how to cache (CacheOptions) and how to expose via WebAPI (WebMetadata, WebResource and WebAction). Because they are frequently accessed, AttributeHelper (CofeServices.PropertyDictionary.AttributeHelper) provide cache access of these attributes.
It's annoying to pass dozens of individual properties around, so there is PropertyDefinitions. PropertyDefinitions is a generic class that takes an enum (e.g. CofeDirectoryProperies). By default the PropertyDefinition include all properties in the specific category, using another constructor can filter some away. Cofe/CofeDB/CofeWS all have PropertyDefinitions static class, which contains all definitions defined in that library. PropertyDefinitionExpression is for lookup a property in database.
A PropertyPair is a property and its value, PropertyPair.Value is in object type so you have to typed it using ValueAs<T>() method. PropertyPair.IsValid is for validating if the value matchs the PropertyTypeAttributes of the property.
PropertyDictionary
PropertyDictionary (CofeServices.PropertyDictionary) is a dictionary for all PropertyDefinitions and Properties, you can lookup PropertyDefinitions from a property (GetPropertyDefinition), you can convert among property object, it's id and it's alias string (GetProperty/GetPropertyString), it also contains AttributeHelper and PropertyInvoker.
PropertyDictionary.AttributeHelper
AttributeHelper responsible for caching attributes in particular property, you can GetDefaultValue(), GetAlias(), GetProperty/DatabaseCacheOption() and FindAttribute<t>() from a property.
The most notable attribute for a property is PropertyTypeAttribute, it's used to indicate what value does it return, if an invalid value is returned, the PropertyPair's IsValid return false and this will fail when SetProperty.
The most common use of PropertyTypeAttribute is to specify using typeof(T) and a default value, ActionAttribute derived PropertyTypeAttribute is used to represent an action, while ListActionAttribute and StreamActionAttribute derived from ActionAttribute is an action that return a specialized type.
In Cofe both Bitmap and Stream are embedded in a container (BitmapContainer and StreamContainer), this is intended to allow one code to run both on Desktop and RT runtime, even one run using System.IO while another running WindowsStorage.
PropertyDictionary.PropertyInvoker
Given the output type, PropertyPair and ParameterDic, PropertyInvoker's Invoke<T>() and InvokeAsync<T>() (or without <T>) invoke an action/func or task in PropertyPair, and return the output value if there's one, in some case, the input ParameterDic also contain output value. PropertyInvoker can invoke an async property in sync mode or vice versa.
Take a look to the following, while the first and the second invokes an action from an entry, the third group actually get the action from the entry and use PropertyInvoker to execute it. The last one cast the action to appropriate output and run it as a task.
EntryFilters
When listing in System.IO, you use a mask to indicate what you want to return, and SearchOption to indicate whether to search subdirectory. In Cofe, because of so many properties, you use EntryFilters. EntryFilters is used to filter a set of entries using a property. Consider a scenario like:
/api/parse?path={cofe}&rel=list&filterString=name:abc OR name:cde type:txt OR type:cs OR type:pas size:>100 subdir:true
in web url, after parsing using IFilterParser, it converts to:
When database is not enabled, EntryFilter's Match() method is used to return whether CurrentEntry is matched, and in the case of directory it returns whether to search sub-directory as well. Most property uses PropertyEntryFilter, which obtain the specified value from the entry. PropertyEntryFilter support < and > operator for numbers and datatime.
While Matching sub-entries (whether to search subdirectory for entries) is OR based, matching entries in EntryFilters are matched in AND based, that means the entry is matched only if all EntryFilters return Yes. When or is needed, one have to embed it into OrEntryFilter, e.g.
new OrEntryFilter(new NameEntryFilter("abc"), new NameEntryFilter("cde")).
When working with database, this becomes impossible, consider if I want to search the whole database for certain criteria, I'll have to materialize every entry in database and compare its values. This is the reason that in every PropertyDefinitions theres a PropertyExpressionDefinition, which provides expression (Func<CofeSystemInfo, T>) to lookup a variable in database. By default all properties is lookup from the PropertyPair_{type} table (where type depended on property type), if the property is cached in ORM, (e.g. ImageProperties.CameraMaker), it have to be specify explicitly, for examples:
EntryOptionFilterParser
EntryFilterParser (CofeServices.EntryFilterParser) is responsible to convert a Filter string to Entry / OptionFilters and vice versa, it construct filter using it's FilterConstructor.TryConstruct<T>() method, which ask lookup the appropriate IFilterFactory and construct from there. EntryFilterParser also contains PropertySerializer which responsible to convert a Key/Value pair to/from string.
Entry (CofeSystemInfo)
Entry in Cofe usually indicate the interface ICofeSystemInfo and it's child interface like IFileSystemInfo, IMediaInfo and IEntryTypeInfo. The base implementation of them is CofeSystemInfo.
CofeSystemInfo and it's sub-implementations, like Cofe.IO.FileInfo, has two purpose. One is to implement the interfaces so user can access the properties and methods through the interfaces. The another is to participate in EntityFramework CodeFirst, so some of the properties can be written as named column in appropriate table, e.g. FileInfo(Id, Type, FileAttributes, Md5), instead of writing in a typed table, e.g. PropertyPair_Int16 (PropertyId, Id, ORMValue).
Unlike System.IO, Cofe is designed to support other third-party plugins so it's not possible for a implementer to implement all possible interfaces, for examples, Cofe.IO support compression archives (via SevenZipSharp), so a FileInfo may be expected to implement IDirectoryInfo. This is not possible so I added a mechanism called IDynamicInterface, which adds Is<T>() and As<T>() methods to CofeSystemInfo, so if you call zipFile.As<IDirectoryInfo>(), you gets a DirectoryInfo which implements IDirectoryInfo.
The construction of PropertyHost and specific type of ICofeSystemInfo is done in CofeServices.IEntryContructor. Converting a PropertyProvider to IPropertyHost involves creating of property cache and querying secondary property providers, while Converting IPropertyHost to ICofeSystemInfo involves querying the dynamic interface provider to create the appropriate container.
This may sound strange because the FileInfo shouldn't contain properties required in DirectoryInfo. The implementer of an entry is actually a skin. If you open FileInfo.cs you can find something like the following in the getter of a property:
return Properties.Behaviors.getProperty<string>(CofeStreamProperties.Md5).
The Properties shown above is a IPropertyHost, which contains the cached variables and the means of getting, setting and invoking a noncached variables, so the process casting from File to DirectoryInfo is just create a blank DirectoryInfo and assign the PropertyHost object.
PropertyHost
PropertHost is a host of all supported PropertyProvides, which includes PropertyCache, Primary and Secondary PropertyProviders. It also contains PropertyBehaviorManager (IPropertyHost.Behaviors), which responsible for getting, setting and invoking properties.
There is one PrimaryPropertyProvider per PropertyHost, and it must provides all basic required properties for the entry to be functional, including ParseName and Type. In CofeIO implementation it's the SystemIOPropertyProvider (or WindowStoragePropertyProvider) that talks with underlying SystemIO.
PropertyHost exists in a connected state or disconnected state. When connected, PrimaryPP is assigned and the general property access (get/set) strategy will be Cache -> Primary -> SecondaryPPs. When disconnected, PrimaryPP is null and the sequence becomes Cache -> SecondaryPPs -> Connect() -> PrimaryPP. This allows properties from database written to PropertyCache and access from there without connecting to the backstore.
SeconaryPropertyProviders (SeconaryPPs) are generated when PropertyHost is constructing, it ask every ISecondaryPropertyProviderFactory if they can create new PropertyProviders for this IPropertyHost. For examples, ExifPropertyProvider is attached when name is end with .jpg and CofeStreamProperties.OpenStreamAsync is supported.
PropertyBehaviorManager responsible for access (get/set) and invoke of property. Based on InvokeBehaviorAttribute of the property, which included HandleOnes, ReturnMin/MaxOnly and AggregateResult), it create different IGetBehavior or ISetBehavior to handle the request. For invocation, it first get the property, depended on how it's defined in the attribute, it can be Action<PD>, Func<PD, T>, Func<PD, Task> or Func<PD, Task<T>>, depended on sync/async and whether have return value T. PD is ParameterDic, it is a string object dictionary, used for both passing input parameters and sometimes some additional output like IsHandled, or in the case of listing with pagination, TotalPages and TotalItems. Although ParameterDic is a string object dictionary, in CofeWS it support string parameter in url only.
Implementation:
The implementation of entry properties are really verbose, if you want to implement your own entry types and properties, you have to:
- Define a property enum (e.g. CofeProperties), create property definition (CofePropertyDefinitions, which is PropertyDefinitionBase<CofeProperties>)
- Create PropertyProvider to return values (Primary for new entry types, Secondary for existing entry types)
- Define new interface (e.g. IFileInfo) and the means to provide the interface (IDynamicInterfaceProvider)
- Define the implements (FileInfo), participate in the Database table construction (IDatabaseConfigurator) and EntityFramework Migration.
- Define the volume factories and diretory listers.
I hope this can be automated a bit in future.
EntryList
EntryList is for representing multiple entries, it's inherited from ICofeDirectoryInfo so you can use it's GetCofeSystemInfosAsync() or EnumerateCofeSystemInfos() methods to access it contents (actual content in auto, link in custom)
There are two kinds of entry lists:
Custom entry lists allow user to specify the content of an Entrylists by adding them manually. User calls ICustomEntryList.AddLink to link an entry to a CustomEntryList, which will create an IEntryLink object that have it's Entry property linked to the actual target entry. When database is enabled, this link is represented in DatabaseEntry, just like an entry. It's possible to create an entryLink to another CustomEntryList, this is what happened when you called IEntryList.CreateLinkFolder() method, it calls createAsync() and return an IEntryLink.
CustomEntryList can be created using EntryFE.NewEntryList() method, once it is make parsable using EntryListFE.MakeParsable() method, it is accessible as a volume.
AutoEntryList means the entries are automatically polled, using Entry and Option Filters. It's mainly for use in search.
AutoEntryList can be created using EntryFE.SearchAsync() method with a filter string, the filter string is passed to IEntryOptionFilterParser to convert into Entry and OptionFilters.
EntryTypeInfo
There is a SystemVolume named {EntryTypeInfo} when Cofe starts, it represent the parent directory of all EntryTypes. EntryType is a way to define properties for broad range of entries,one entry can have one entry type only. In database, it's stored as a field in DatabaseEntry.
When an EntryTypeInfo is requested via calling CofeEntryTypeProperties.EntryTypeKey or Info provided by EntryTypeSecondaryPP, it actually calls EntryTypeInfoManager.GetEntryTypeKey() method, which use it's IEntryTypeIdentifier to locate EntryTypeKey and IEntryTypeProvider to create PrimaryPP for the EntryType. It's then passed to EntryConstructor to construct as PropertyHost and entry.
Because EntryTypeInfo is an entry, other external libraries can attach secondary PropertyProviders to the resulting PropertyHost.
TransferManager
Except CofeDirectoryProperties.CreateAsync and CofeEntryLinkProperties.AddLink, entry itself do not provide commands for transferring entries. Instead, CofeServices.TransferManager is used for transfering entries to a directory. TransferManager has Transfer/RenameAsync() methods, when called, look for appropriate ITransferHelper to do the transfer. The default transfer included CopyDirectory, CopyStream, RenameEntry and LinkEntry, which do the transfer by entry by entry based.
Volume
Similar to System.IO, Volumes in Cofe are root root directories of every entry. But unlike SystemIO, whcih every volume represent a drive letter, Cofe does not have concept of drive, it's volumes are defined by it's implementation. For CofeIO, one use root directory as a volume. Because of this, when Cofe is started, there's a system volume (EntryTypeInfo), but there's no non-system volume in CofeServices.VolumeRegistrar, a place which keep track of registered volumes and volume factories.
VolumeFactories are defined by it's implementation, Cofe3 has EntryListVolumeFactory for registering entrylist as volume, while CofeIO has SystemIO/WindowsStorageVolumeFactory for registering volume as disk path. If a volume is registered (e.g. using VolumeFE.MapAsync("CofeIO", "doc", Tuple.Create("VolumePath", RootPath))), a Volume is created with VolumeInfo, DirectoryLister and EventSources. which after that {doc} directory can then be parsable as CofeDirectory.
DirectoryLister is a class that provide the root PropertyProviders of a volume, and parse a partial path (a path without volume id) and return appropriate PrimaryPropertyProvider if found.
EventSources is for monitoring events happened in the particular file system, when the volume is registered, it's event source will be registered in EventHub so when the source raise a event, it passed to the hub and distribute to the listeners.
CofeSettings
The registed VolueInfos can be reused when the application is restarted if it's stored in CofeSettings. CofeServices.CofeSettings is a central place to store all settable settings (there's also ICofeWsSettings too, which inherits ICofeSettings), one can update the property and call CofeServices.SaveAsync(), which will notify all ISaveSettings implementers to save settings to CofeSettings, and all settings will be serialized in _cofe.xml in IsolatedStorage folder. And when CofeServices.RestoreAsync() is called, it will deserialized the settings and notify all IRestoreSettings implmenters to restore settings. Volume(IVolume) are stored as IVolumeInfo, which contains FactoryId, VolumeId and parameter only.
Security
Security can be setup on user role and volume only, not it's sub-directory, so if you want to assign different role requirement for different sub-directory, you will have to create multiple volumes. VolumeFE helper contains GrantPermission(role, volumeId, permission) and RevokePermission(), which calls CofeServices.SecurityManager.Add/RemovePermissionPolicy() with appropriate parameters.
When an entry is parsed, listed, a property or action is called or invoked, CofeServices.SecurityManager.DemandPermission<ivolumeinfo>() is called, and SecurityManager will evaluate it's PermissionPolicy. If there's any match, it will check PermissionPolicy's Required role, and check if the role is included in ISecurityManager.CurrentPrincipal, which is generated by ISecurityManager.UserManager.GetPrincipal(CurrentThread.Identity).
Reading, Writing and Invoking normally require respective permission (R/W/E), but it can be overrided by using RequirePermirrionAttribute. The check take place mostly in PropertyBehaviorManager. The security is designed to deny unauthorize access on web only; after all, if someone has access to the source code they don't need Cofe to access the data they wanted.
While SecurityManager holds IPermissionPolicy, UserManager holds IPrincipalPolicy, which defined what role is given to what identity, one can use AddPrincipalPolicy() and RemovePrincipalPolicy() to update the PrincipalPolicies.
The main use of security is in CofeWs. In WSUserManager, if the user identity is in Forms Authentication, UserManager calls ASP.Net's SimpleRoleProvider for roles, thus you just need to manager the roles in ASP.Net instead of setting PrincipalPolicies.
Both IPermissionPolicy and IPrincipalPolicy are saved in CofeSettings.
Events
Event handling for Cofe system changes is important because to enable CofeSystemWatcher
to report when there's an event, to keep database updated, and to keep the feed system work, it has to be notified when there's a change.
In Cofe, event handling is done by EventHub, it's a hub that accepts an event and broadcasts multiple events in an interval. On one side,
we can use its RegisterEventSource<T>()
to register an IEventSource<T>
, which raises a specific type of
EventArgs
. For example, each Volume
may contain one or more EventSources to raise
a CofeSystemEvent
when an item in the volume is changed, (e.g.,
SystemIOEventSource
). The default implementation of Volume
is
ManualVolumeSource
, which allows the user to raise an event by calling the
RaiseChange
/Created
/Renamed
/Deleted()
methods.
On the other side, we can use its RegisterEventListener<T>()
to register an
IEventListenener<T>
. EventHub routinely broadcasts received events to the listeners every n seconds using
IEventListener
's DisplatchEvent()
.
Both IEventSource
and IEventListener
can unregister itself by using their
CancellationToken
.
A list of EventListener implementations:
CofeSystemWatcher
- Just like FileSystemWatcher
, there's a
CofeSystemWater
to raise an event when CofeSystem changes, CofeSystemWatcher
only registers itself to
the EventHub
when EnableRaisingEvents
is set to true.CofeDBEventListener
- CofeDBEventListener
has an IEventListenerFactory
which enables it to be registered when the first event is dispatched; it refreshes the
LastUpdateTime
/LastListTime
of the parent of the affected entry, expires the affected entry, and updates the entry if
CacheUpdateOption
is Passive
.FeedServices
- CofeWsServices.FeedServices
writes
a fee periodically to the \log directory using ATOM RSS format.
CofeServices
CofeServices
is a static class that provide to all ICofeService
s.
IEntryConstructor
, IPathParserManager
, and ISecurityManaager
are all
ICofeService
s.
CofeServices
contains ServiceLocater
, which is responsible for holding and querying
ICofeService
s.
BootStrapper
The BootStrapper registers all related ICofeService
s to the ServiceLocater.
ManualBootStrapper
and MEFBootStrapper
are now replaced by
ModuleBootStrapper
; all libraries contain a class named RegisterModule
, which contains the
ICofeService
s in that library, all you need is to include these
RegisterModule
s in the constructor of the BootStrapper
(sequence doesn't matter, as
ICofeService
s are sorted when imported) and invoke its Run()
method.
new ManualBootStrapperIODB(false, false).Run();
new ModuleBootStrapper(
true,
new Cofe.Core.RegisterModule(),
new Cofe.Data.RegisterModule(true, CacheUpdateOptions.Manual),
new Cofe.IO.RegisterModule(),
new Cofe.Media.RegisterModule()
).Run();
Beside CofeS
ervices, there's CofeDBServices
and CofeWsServices
as well, which provide services defined in Cofe.Data and Cofe.Web.
Cofe3.Data
CofeDB3 is responsible for caching entries in the database, allow parsing, listing, and searching entries in
the database.
IRepository (Pending to be refactored)
When you call the DatabaseProperties.WriteRepositoryAsync
action to add or update an entry to the database, you are actually calling
IRepository.AddOrUpdate()
. IRepository
talks with the database implementation and it provides update, lookup, and remove for
DatabaseEntry
.
CofeDBServices.RepositoryFactory
is for constructing an IRepository
, which
is responsible for reading, writing, and removing entries (in the PropertyHost
form) to and from
the database. A factory is required because each copy of EFRepository
, the implementation of
IRepository
that uses EntityFramework, contains a DbContext
, which must be released when
EFRepository
is disposed.
using (var Repository = RepositoryFacoty.CreateRepository()) {... }
DatabaseEntry
Because one entry may have more than one implementation, the most logical way is to have a common parent class to hold these implementations, and this is
DatabaseEntry
. This class is defined in Cofe.Core
,
and contains implementation which contains CofeSystemInfo
s, and
ExtendedProperties
, which contains non-ORM (Object Relational Mapping, e.g.,
FileInfo.Length
) property values. To reduce redundancy, properties in
CofeSystemInfo
, like ParseName
, Label
,
CofeAttributes
, are moved to DatabaseEntry.DatabaseEntryFactory
.
But how to construct these DatabaseEntry
s? CofeDBServices.DatabaseEntryFactory
provides a bi-directional way to construct them.
When constructing from IPropertyHost
to DatabaseEntry
(to EF),
DatabaseEntryFactory
asks PropertyInterfaceManager
for each castable type, and creates an
implementation (e.g., FileInfo
) when applicable. This part is easy because it's the EF side that needs only the ORM property for each implementation.
When constructing from DatabaseEntry (from EF) to IPropertyHost
, each implementation has a
PropertyCache
that only caches the ORM properties, while the non-ORM properties
are in ExtendedProperties
. DatabaseEntryFactory
has to group the
PropertyCache
into one, write the properties in the root DatabaseEntry
and
ExtendedProperties
to the PropertyCache
.
DatabaseConfigurator
The DbContext of Cofe is CofeDbContext
, to allow an external library to participate when constructing the model and database,
CofeDbContext
loops through each IDatabaseConfigurator
when
ModelCreating
.
The construction of the CofeDbContext
initiator is best done through
CofeDbContext.Initializer
, which uses migration to create the database.
I have manually modified the migration file after it's automatically generated, added
cascade delete to the child classes of CofeSystemInfo
and PropertyPair
so
a child row is deleted with the parent row, instead of complaining a child row is linking the parent row. I also added a
SQL statement which makes DatabaseEntry.ParseName
unique.
A list of commands in Migrations\CommandLine.txt is available for how to generate migrations and use it to update
a database manually.
Cofe3.Web
Cofe3.Web provides Feed
(FeedServices
) and Serialization/Deserialization services (HttpMessageFormatterManager
) to Cofe.Web.Services, while
the Cofe.Web.Services library is an ASP.NET application that includes the APIController for serving the web service.
FeedServices
Use CofeWsServices.Feeds.GetFeedServices<T>()
to access the
IFeedServices<T>
object, which contains anything related to a feed for
a particular (T
) typed EventArgs
. FeedServices
contains
FeedFileSystemHelper
, FeedUpdater
, FeedBuilder
,
FeedLinkHelper
, and FeedRepository
.
FeedFileSystemHelper
FeedServices
uses FeedFileSystemHelper
as a class for providing IO access, the actual path of feeds
is also defined in this class.
FeedUpdater
When FeedServices.Updater
(IFeedUpdater
) is started, it listens to events from EventHub and writes to its
EventBuffer
, which stores EventLog<EventArgs>
. Once every n seconds, it calls its Update thread, which Runs the script command
QueryingEventsCommand<T>
shown on the diagram. When this command completes it will emit
IdentifyingRecentEventFeedsCommand<T>
, and it will be run and so on, until
NotifyingListeners<T>
, which will emit ResultComamnds.OK
, and thus stops the script command. This implementation is taken from the book,
REST in Practice.
FeedBuilder
FeedBuilder
is responsible for loading and building Feed<T>
.
There is always a RecentEventFeed
and ArchiveFeed
s, where
if RecentEventFeed
is full it is copied as an ArchiveFeed
, and a new
RecentEventFeed
is created. These feeds are stored in \log\current (as
{FeedGuid}.atom) and \log\archive (as {Feed#}.atom) as an
ATOM file by default. For each feed item added, the entry is written separately in
\log\entry (as {EntryGuid}.atom).
The Feed
object contains the FromFileName()
, FromFeedId()
, and
Save()
methods to load and save the SyndicationFeed
object.
FeedLinkHelper
Given LinkType and Link formation, FeedLinkHelper
creates a
SyndicationLink
object for adding to the result SyndicationFeed
, based on the template defined in
FeedBuilder
. Creatable links include Entry (CofeSystemInfo
), Event (SyndicationItem
), and Feed (SyndicationFeed
).
FeedRepository
FeedRepository
is for external classes to access an Event or Feed stored in memory and file system.
FeedRepository
returns a SyndicationItem
or Feed, we can serialize the object
itself, or use the included CofeServices.HttpMessageFormatter
to create
an HttpResponseMessage
.
HttpMessageFormatterManager
CofeServices.HttpMessageFormatter
is responsible for both Serializing and Deserializing
an entry to HttpResponseMessage
or from HttpRequestMessage
, which are used in WebAPI. The manager class is an interface class, the actual conversion is done by
the implemented IHttpRequestMessageFormatter
and IHttpResponseFormatter
.