Migrate Newtonsoft JsonConverter to JsonConverter<T> - json.net

I have the following interface
public interface IApiResult<TResult> : IApiResult
{
TResult Result { get; set; }
}
with a concrete class like this
public class ApiResult<TResult> : ApiResult, IApiResult<TResult>
{
public ApiResult( TResult result ) : base() {
Result = result;
}
public TResult Result { get; set; }
}
When I used Newtonsoft json library I used a JsonConverter to manage polimorphic serialization and deserialization this way
public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer ) {
var obj = JObject.Load( reader );
var apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Check if is an IApiResult<T>
if (obj.TryGetValue( nameof( ApiResult<object>.Result ), StringComparison.InvariantCultureIgnoreCase, out var jResult )) {
//Retrieve the result property type in order to create the proper apiResult object
var prop = objectType.GetProperty( nameof( ApiResult<object>.Result ) );
var value = jResult.ToObject( prop.PropertyType, serializer );
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
//Set the messages
var jMessages = obj.GetValue( nameof( ApiResult.ApiResult.Messages ), StringComparison.InvariantCultureIgnoreCase ).ToObject<JObject[]>();
apiResult.Messages = DeserializeReasons( jMessages );
return apiResult;
}
How can I migrate this code to System.Text.Json?
UPDATE
My biggest problem is with the JObject.TryGetvalue function. This function would have returned a deserialized object that let me understood the type. Since that, I was only using some reflection to understand the type of ApiResult<T>.
With the actual UTF8JsonReader class I am only able to read token by token so I can not replicate the previous behavior.

Your question boils down to, Inside JsonConverter<T>.Read(), how can I scan forward in, or load the contents of, a Utf8JsonReader to determine the polymorphic type of object to deserialize without having to manually deserialize token by token as shown in the documentation example?
As of .NET 6 you have a couple options to accomplish this.
Firstly you can copy the Utf8JsonReader struct and scan forward in the copy until you find the property or properties you want. The original, incoming Utf8JsonReader will be unchanged and still point to the beginning of the incoming JSON value. System.Text.Json will always preload the entire JSON object, array or primitive to be deserialized before calling JsonConverter<T>.Read() so you can be certain the require values are present.
To do this, introduce the following extension methods:
public static partial class JsonExtensions
{
public delegate TValue? DeserializeValue<TValue>(ref Utf8JsonReader reader, JsonSerializerOptions options);
///Scan forward in a copy of the Utf8JsonReader to find a property with the specified name at the current depth, and return its value.
///The Utf8JsonReader is not passed by reference so the state of the caller's reader is unchanged.
///This method should only be called inside JsonConverter<T>.Read(), at which point the entire JSON for the object being read should have been pre-loaded
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, out TValue? value) =>
reader.TryGetPropertyValue<TValue>(name, comparison, options, (ref Utf8JsonReader r, JsonSerializerOptions o) => JsonSerializer.Deserialize<TValue>(ref r, o), out value);
public static bool TryGetPropertyValue<TValue>(this Utf8JsonReader reader, string name, StringComparison comparison, JsonSerializerOptions options, DeserializeValue<TValue> deserialize, out TValue? value)
{
if (reader.TokenType == JsonTokenType.Null)
goto fail;
else if (reader.TokenType == JsonTokenType.StartObject)
reader.ReadAndAssert();
do
{
if (reader.TokenType != JsonTokenType.PropertyName)
throw new JsonException();
var currentName = reader.GetString();
reader.ReadAndAssert();
if (String.Equals(name, currentName, comparison))
{
value = deserialize(ref reader, options);
return true;
}
else
{
reader.Skip();
}
}
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject);
fail:
value = default;
return false;
}
static void ReadAndAssert(ref this Utf8JsonReader reader)
{
if (!reader.Read())
throw new JsonException();
}
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
And now your Newtonsoft converter might be rewritten to look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
record MessagesDTO(Message [] Messages); // Message is the presumed type the array elements of ApiResult.ApiResult.Messages, which is not shown in your question.
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
IApiResult? apiResult = null;
if (reader.TryGetPropertyValue(nameof( ApiResult<object>.Result ), StringComparison.OrdinalIgnoreCase, options,
(ref Utf8JsonReader r, JsonSerializerOptions o) =>
{
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
return (Value : JsonSerializer.Deserialize(ref r, prop.PropertyType, o), Property : prop);
},
out var tuple))
{
var rType = typeof( ApiResult<> ).MakeGenericType( tuple.Property.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>().ThrowOnNull();
tuple.Property.SetValue( apiResult, tuple.Value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
// Now consume the contents of the Utf8JsonReader by deserializing to MessagesDTO.
var dto = JsonSerializer.Deserialize<MessagesDTO>(ref reader, options);
apiResult.Messages = dto?.Messages ?? Array.Empty<Message>();
return apiResult;
}
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
Notes:
This approach works well when scanning forward for a single simple property, e.g. a type discriminator string. If the type discriminator string is likely to be at the beginning of the JSON object it will be quite efficient. (This does not seem to apply in your case.)
JsonConverter<T>.Read() must completely consume the incoming token. E.g. if the incoming token is of type JsonTokenType.StartObject then, when exiting, the reader must be positioned on a token of type JsonTokenType.EndObject at the same depth. Thus if you only scan forward in copies of the incoming Utf8JsonWriter you must advance the incoming reader to the end of the current token by calling reader.Skip() before exiting.
Both Json.NET and System.Text.Json use StringComparison.OrdinalIgnoreCase for case-invariant property name matching, so I recommend doing so as well.
Secondly, you can load the contents of your Utf8JsonReader into a JsonDocument or JsonNode, query its properties, then deserialize to your final desired type with one of the JsonSerializer.Deserialzie() overloads that accepts a JSON document or node.
Using this approach with JsonObject in place of JObject, your converter might look something like:
public class ApiResultConverter : System.Text.Json.Serialization.JsonConverter<IApiResult>
{
public override IApiResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var obj = JsonNode.Parse(ref reader, new JsonNodeOptions { PropertyNameCaseInsensitive = true })?.AsObject();
if (obj == null)
return null; // Or throw JsonException() if you prefer
IApiResult? apiResult = null;
if (obj.TryGetPropertyValue( nameof( ApiResult<object>.Result ), out var jResult ))
{
//Retrieve the result property type in order to create the proper apiResult object
var prop = typeToConvert.GetProperty( nameof( ApiResult<object>.Result ) ).ThrowOnNull();
var value = JsonSerializer.Deserialize(jResult, prop.PropertyType, options);
var rType = typeof( ApiResult<> ).MakeGenericType( prop.PropertyType );
apiResult = Activator.CreateInstance( rType ).As<IApiResult>();
prop.SetValue( apiResult, value );
}
if (apiResult == null)
apiResult = new ApiResult.ApiResult().As<IApiResult>();
//Set the messages
JsonObject? []? messages = obj[nameof( ApiResult.Messages )]?.AsArray()?.Select(i => i?.AsObject())?.ToArray();
apiResult.Messages = DeserializeReasons(messages); // Not shown in your question
return apiResult;
}
static JsonObject? [] DeserializeReasons(JsonObject? []? messages) => messages == null ? Array.Empty<JsonObject>() : messages;
public override void Write(Utf8JsonWriter writer, IApiResult value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
public static partial class ObjectExtensions
{
public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}
Notes:
This approach works well when you have multiple properties (possibly with complex values) to search for and load during the conversion process.
By loading the JsonObject with JsonNodeOptions.PropertyNameCaseInsensitive = true, all property name lookups in the deserialized JsonNode hierarchy will be case-insensitive (using StringComparer.OrdinalIgnoreCase matching as shown in the source).
Since your question does not include a compilable example, the above converters are untested.

Related

is it a good idea to do transformation in a database model with ASP.Net

we are a small development team.
We develop in ASP.NET and we are starting to use generic controllers and services.
The goal is to have solid methods for things that are repetitive.
What we ask ourselves is if it is a good idea to do some transformation in the data models to allow us to reuse our functions that we know are working?
Exemple: we have a combobox and we want to manage the display and search. It's always the same and redundant.
This is my class
[Table("stage.Test")]
public partial class Test : IBaseEntity, ICombobox
{
public virtual Product Product { get; set; }
public string nom { get; set; }
public string prenom { get; set; }
public string title { get; set; }
[NotMapped]
public virtual string AffichageCombobox => nom + prenom;
[NotMapped]
public virtual string TexteRecherche => Product.Gabarit.Description;
}
as you can see i have two columns with the tag [NotMapped]. These are the columns in the interface ICombobox
public interface ICombobox
{
string AffichageCombobox { get;}
string TexteRecherche { get; }
}
this is the first service where I use one of my two columns which redirects to other columns. [We use the column "AffichageCombobox" from the model]
public List<ComboboxViewModel> GetComboboxViewModel(int? id, bool actifseulement, string text)
{
var query = _requestDatabaseService.GetComboboxQuery<T>(id, actifseulement, text);
var list = query.Select(table => new ComboboxViewModel
{
Id = table.Id,
AffichageCombobox = table.DateHFin == null ? table.AffichageCombobox : table.AffichageCombobox + " (inactif)"
}).ToList();
return list;
}
This is the RequestDatabaseService [We use the column "TexteRecherche" from the model]
public List<T> GetComboboxQuery<T>(int? id, bool actifseulement, string text) where T : class, IBaseEntity, ICombobox
{
text = text.ToLower();
var list = _dbContext.Set<T>()
.If(id.HasValue,
q => q.Where(x => x.Id == id))
.If(actifseulement,
q => q.Where(x => x.DateHFin == null))
.If(text != "",
q => q.Where(x => x.TexteRecherche.ToLower() == text))
.ToList();
return list;
}
As you can see, I am using an interface to add columns to redirect to the correct columns to my data model to avoid overriding my methods for two column.
Is it a good idea, a good practice ?
What do you think is the best practice if we want to do generic functions, but the columns are not called the same way?
Thank you!
Your solution has a lot of weaknesses
You have extended Model to handle specific UI cases. In my opinion it is bad practice.
Your virtual properties will not work in LINQ query. EF translates only Expression because it canot look into compiled property body.
What we can do here is simplifying of building such comboboxes. I have defind set fo extensions which can be reused for such scenarios. Sorry if there some mistakes, written from memory.
How it can be used:
Assuming that GetComboboxViewModel is not in generic class
public List<ComboboxViewModel> GetComboboxViewModel(int? id, bool actifseulement, string text)
{
// uncover DbContext. All that we need is IQueryable<Test>
var ctx = _requestDatabaseService.GetContext();
var query = ctx.Test.AsQueryable();
var comboItems = query
.FilterItems(id, actifseulement)
.GetComboboxQuery(text, e => e.Product.Gabarit.Description, e => e.nom + e.prenom)
.ToList();
return comboItems;
}
Think about this solution and yes, we can register somewhere pair of Lmbdas Dictionary<Type, (LambdaExpression: searchProp, LambdaExpression: displayProp)> and dynamically build call above.
Realisation:
public static class QueryableExtensions
{
// more simlified version for filtering
public static IQueryable<T> WhereIf(this IQueryable<T> query, bool condition, Expression<Func<T, bool>> predicate)
{
return condition ? query.Where(predicate) : query;
}
// handy function for filtering
public static IQueryable<T> FilterItems<T>(this IQueryable<T> query, int? id, bool onlyActive)
where T : IBaseEntity
{
query = query
.WhereIf(id.HasValue, x => x.Id == id)
.WhereIf(onlyActive, x => x.DateHFin == null)
return query;
}
// dynamic generation of filtering and projection
public static IQueryable<ComboboxViewModel> GetComboboxQuery<T>(this IQueryable<T> query, string text, Expression<Func<T, string>> searchProp, Expression<Func<T, string>> dsiplayProp)
where T : IBaseEntity
{
if (!string.IsNullOrEmpty(text))
{
text = text.ToLower();
// defining search pattern
// this also extension point, you may use here `Contains` or FullText search functions
Expression<Func<string, string, bool>> filterFunc = (s, t) => s.ToLower() == t;
// reusing parameter from searchProp lambda
var param = searchProp.Parameters[0];
// applying pattern to searchprop
var filterBody = ExpressionReplacer.GetBody(filterFunc, searchProp.Body, Expression.Constant(text));
// applying generated filter
var filterPredicate = Expression.Lambda<Func<T, bool>>(filterBody, param);
query = query.Where(filterPredicate);
}
// defining template for Select
Expression<Func<T, string, ComboboxViewModel>> createTemplate = (entity, dp) => new ComboboxViewModel
{
Id = entity.Id,
AffichageCombobox = entity.DateHFin == null ? dp : dp + " (inactif)"
};
// reusing parameter from dsiplayProp lambda
var entityParam = dsiplayProp.Parameters[0];
// injecting dsiplayProp into createTemplate
var selectBody = ExpressionReplacer.GetBody(createTemplate, entityParam, dsiplayProp.Body);
var selectLambda = Expression.Lambda<Func<T, ComboboxViewModel>>(selectBody, entityParam);
// applying projection
var comboQuery = query.Select(selectLambda);
return comboQuery;
}
// helper class for correcting expressions
class ExpressionReplacer : ExpressionVisitor
{
readonly IDictionary<Expression, Expression> _replaceMap;
public ExpressionReplacer(IDictionary<Expression, Expression> replaceMap)
{
_replaceMap = replaceMap ?? throw new ArgumentNullException(nameof(replaceMap));
}
public override Expression Visit(Expression exp)
{
if (exp != null && _replaceMap.TryGetValue(exp, out var replacement))
return replacement;
return base.Visit(exp);
}
public static Expression Replace(Expression expr, Expression toReplace, Expression toExpr)
{
return new ExpressionReplacer(new Dictionary<Expression, Expression> { { toReplace, toExpr } }).Visit(expr);
}
public static Expression Replace(Expression expr, IDictionary<Expression, Expression> replaceMap)
{
return new ExpressionReplacer(replaceMap).Visit(expr);
}
public static Expression GetBody(LambdaExpression lambda, params Expression[] toReplace)
{
if (lambda.Parameters.Count != toReplace.Length)
throw new InvalidOperationException();
return new ExpressionReplacer(Enumerable.Range(0, lambda.Parameters.Count)
.ToDictionary(i => (Expression) lambda.Parameters[i], i => toReplace[i])).Visit(lambda.Body);
}
}
}
Well, after writing this sample, I think, it can be cardinally simplified by using LINQKit. Will post another answer with LINQKit usage if you are interested,

How to use tempdata to return error message

I am trying to use temp data to return messages but it gives an error :
InvalidOperationException: The 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer' cannot serialize an object of type
I am already using
services.AddMvc().AddSessionStateTempDataProvider();
app.UseSession()
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
});
Here is my shared
FlashMessages.cshtml :
#using EnduroMotors.ViewModels
#{
var errorMessages = TempData["_error_messages"] as List<FlashMessageModel>
?? new List<FlashMessageModel>();
var warningMessages = TempData["_warning_messages"] as
List<FlashMessageModel> ?? new List<FlashMessageModel>();
var successMessages = TempData["_success_messages"] as
List<FlashMessageModel> ?? new List<FlashMessageModel>();
var infoMessages = TempData["_info_messages"] as List<FlashMessageModel> ??
new List<FlashMessageModel>();
}
Here is my viewmodel :
FlashMessageModel
public class FlashMessageModel
{
public string Title { get; set; }
public string Message { get; set; }
}
And here is use in controller :
Controller
protected void ShowSuccessMessage(string message, string title =
"Success!")
{
var messages =
(List<FlashMessageModel>)TempData["_success_messages"] ?? new
List<FlashMessageModel>();
messages.Add(new FlashMessageModel
{
Title = title,
Message = message
});
TempData["_success_messages"] = messages;
}
using this with return
ShowSuccessMessage("You have completed.");
it should show success message in index with #{Html.RenderPartial("FlashMessages");} but instead it gives
InvalidOperationException: The 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer' cannot serialize an object of type 'EnduroMotors.ViewModels.FlashMessageModel'.
Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer.EnsureObjectCanBeSerialized(object item)
TempData serialises objects to strings for storage. It supports string, int and boolean types natively. If you want to store more complex types, you have to serialise (and deserialise) them yourself. JSON is the recommended format. The following extension methods use the JSON.NET JsonConvert static methods to do this:
public static class TempDataExtensions
{
public static void Set<T>(this ITempDataDictionary tempData, string key, T value) where T : class
{
tempData[key] = JsonConvert.SerializeObject(value);
}
public static T Get<T>(this ITempDataDictionary tempData, string key) where T : class
{
tempData.TryGetValue(key, out object o);
return o ?? JsonConvert.DeserializeObject<T>((string)o);
}
}
You can read more about this here: https://www.learnrazorpages.com/razor-pages/tempdata#limitations

Cannot Deserialize Recursively a Class Hierarchy Using JSON.NET

I have a class hierarchy like this:
class Rule { }
class Condition { List<Rule> Rules { get; set; } }
Forget about the remaining properties. I need to deserialize from a JSON string, using a custom JsonConverter. The problem is, I have code for each specific case, but I cannot have it ran recursively, for taking care of the Rules property, each of its elements can be a Condition too.
My code looks like this (ReadJson method):
var jo = JObject.Load(reader);
Rule rule = null;
if (jo["condition"] == null)
{
rule = new Rule();
//fill the properties for rule
}
else
{
rule = new Condition();
//I now want the converter to go through all the values in jo["rules"] and turn them into Rules or Conditions
}
What is the best way to achieve this? I tried to get the JSON for the remaining part, if the object is found to be a Condition:
var json = jo.GetValue("rule").ToString();
But I cannot deserialize it like this, it throws an exception:
var rules = JsonConvert.DeserializeObject<Rule[]>(json, this);
The exception is: JsonReaderException : Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1.
Any ideas?
You're not far from having it working. After you instantiate correct type of object based on the presence or absence of the condition property in the JSON, you can populate the instance using the serializer.Populate method. This should take care of the recursion automatically. You do need to pass a new JsonReader instance to Populate, which you can create using jo.CreateReader().
Here is what the converter should look like:
public class RuleConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Rule).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jo = JObject.Load(reader);
Rule rule = null;
if (jo["condition"] == null)
{
rule = new Rule();
}
else
{
rule = new Condition();
}
serializer.Populate(jo.CreateReader(), rule);
return rule;
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Here is a working example: https://dotnetfiddle.net/SHctMo

Ignore ExpandoObject Properties with JSON.Net Serializer Settings

Is it possible to ignore ExpandoObject properties, in particular those of Delegate type, when using JsonConvert(expando, formatting, serializerSettings)?
Essentially I'm trying to ignore all parsing of the func property in this example expando object:
//{
// func: () => {}
//}
Action func = () => {};
dynamic expando = new ExpandoObject();
expando.func = func;
// should be empty object {}
string json = JsonConvert(expando, formatting, serializerSettings);
The first thing I tried was overriding the converter. Unfortunately this doesn't work, as I see CanConvert called recursively for Action -> DelegateEntry -> some generic type -> RuntimeMethodInfo.
private class ExpandoObjectIgnoreConverter : ExpandoObjectConverter
{
public override bool CanConvert(Type objectType)
{
if (typeof(Delegate).IsAssignableFrom(objectType))
{
return false;
}
return base.CanConvert(objectType);
}
}
A method that works is using an error handler in serialization settings and a contract resolver. When I throw the error, all further processing of the property is ignored, i.e. Action -> DelegateEntry -> some generic type -> RuntimeMethodInfo. However, I'd like to do this more elegantly than throwing an exception if possible.
Error handler:
serializationSettings.Error = (sender, args) =>
{
if (args.ErrorContext.Error is InvalidCastException)
{
args.ErrorContext.Handled = true;
}
}
Contract resolver:
private class ExpandoObjectContractResolver : DefaultContractResolver
{
public override JsonContract ResolveContract(Type type)
{
if (typeof(Delegate).IsAssignableFrom(type))
{
throw new InvalidCastException();
}
else
{
return base.ResolveContract(type);
}
}
}
I'm using the edge library to script nodejs from within a C# process. I'm trying to remove functions from the returned javascript objects from within C#, as they are assigned a Delegate type that doesn't play nicely with JsonConvert.
ExpandoObjectConverter does not have any custom code to write an ExpandoObject. Instead it overrides JsonConverter.CanWrite to return false thereby allowing the expando to be serialized generically as an IDictionary<string, object>.
Thus you can override CanWrite and WriteJson() yourself to filter undesired key/value pairs before serialization:
public class FilteredExpandoObjectConverter : ExpandoObjectConverter
{
public override bool CanWrite { get { return true; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var expando = (IDictionary<string, object>)value;
var dictionary = expando
.Where(p => !(p.Value is System.Delegate))
.ToDictionary(p => p.Key, p => p.Value);
serializer.Serialize(writer, dictionary);
}
}
Then use the converter in settings as follows:
var formatting = Formatting.Indented;
var serializerSettings = new JsonSerializerSettings
{
Converters = { new FilteredExpandoObjectConverter() },
};
var json = JsonConvert.SerializeObject(expando, formatting, serializerSettings);
Note this will only filter delegates values directly owned by an ExpandoObject. If you have a collection containing some delegates, or delegate-valued members in some POCO, they will not be filtered by this code.
Sample fiddle.

Create RSS feed in MVC4/WebAPI

I'm looking for the best way to create an RSS feed via MVC4 (and/or WebAPI). This post seemed the most applicable http://www.strathweb.com/2012/04/rss-atom-mediatypeformatter-for-asp-net-webapi/. But it was written in the pre-Release days of WebAPI. I've used Nuget to bring all packages up-to-date but attempting to build the project tosses:
Error 2 The type or namespace name 'FormatterContext' could not be found (are you missing a using directive or an assembly reference?) G:\Code\MvcApplication-atomFormatter\MvcApplication-atomFormatter\SyndicationFeedFormatter.cs 38 129 MvcApplication_syndicationFeedFormatter
I've found a number of articles explaining that the MediaTypeFormatter has changed significantly since beta but I have found details on the adjustments required to the code snippet in question.
Is there an updated resource showing the construction of an RSSFormatter?
thx
Yes I wrote that tutorial against Beta.
Below is the code updated to RTM version.
One advice, if I may, is that this example uses a simple "whitelist" of concrete types for which RSS/Atom feed is build (in this case my Url model). Ideally in more complex scenarios, you'd have the formatter set up against an interface, rather than a concrete type, and have all Models which are supposed to be exposed as RSS to implement that interface.
Hope this helps.
public class SyndicationFeedFormatter : MediaTypeFormatter
{
private readonly string atom = "application/atom+xml";
private readonly string rss = "application/rss+xml";
public SyndicationFeedFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue(atom));
SupportedMediaTypes.Add(new MediaTypeHeaderValue(rss));
}
Func<Type, bool> SupportedType = (type) =>
{
if (type == typeof(Url) || type == typeof(IEnumerable<Url>))
return true;
else
return false;
};
public override bool CanReadType(Type type)
{
return SupportedType(type);
}
public override bool CanWriteType(Type type)
{
return SupportedType(type);
}
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
return Task.Factory.StartNew(() =>
{
if (type == typeof(Url) || type == typeof(IEnumerable<Url>))
BuildSyndicationFeed(value, writeStream, content.Headers.ContentType.MediaType);
});
}
private void BuildSyndicationFeed(object models, Stream stream, string contenttype)
{
List<SyndicationItem> items = new List<SyndicationItem>();
var feed = new SyndicationFeed()
{
Title = new TextSyndicationContent("My Feed")
};
if (models is IEnumerable<Url>)
{
var enumerator = ((IEnumerable<Url>)models).GetEnumerator();
while (enumerator.MoveNext())
{
items.Add(BuildSyndicationItem(enumerator.Current));
}
}
else
{
items.Add(BuildSyndicationItem((Url)models));
}
feed.Items = items;
using (XmlWriter writer = XmlWriter.Create(stream))
{
if (string.Equals(contenttype, atom))
{
Atom10FeedFormatter atomformatter = new Atom10FeedFormatter(feed);
atomformatter.WriteTo(writer);
}
else
{
Rss20FeedFormatter rssformatter = new Rss20FeedFormatter(feed);
rssformatter.WriteTo(writer);
}
}
}
private SyndicationItem BuildSyndicationItem(Url u)
{
var item = new SyndicationItem()
{
Title = new TextSyndicationContent(u.Title),
BaseUri = new Uri(u.Address),
LastUpdatedTime = u.CreatedAt,
Content = new TextSyndicationContent(u.Description)
};
item.Authors.Add(new SyndicationPerson() { Name = u.CreatedBy });
return item;
}
}

Resources