Skip to content

Instantly share code, notes, and snippets.

@mohammed-io
Last active August 21, 2019 13:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mohammed-io/d27677581be00699caca634d520fb535 to your computer and use it in GitHub Desktop.
Save mohammed-io/d27677581be00699caca634d520fb535 to your computer and use it in GitHub Desktop.
A Multi-format response for ASP.net Core
/**
* Licensed under MIT license.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
namespace MohammedIo.MultiFormat
{
using FormatsDictionary = Dictionary<string, Func<object>>;
public class MultiFormatResult : IActionResult
{
private readonly FormatsDictionary _formats;
public MultiFormatResult() : this(new FormatsDictionary())
{
}
public MultiFormatResult(FormatsDictionary formats)
{
_formats = formats;
}
public Func<object> this[string index]
{
get => _formats[index];
set => _formats[index] = value;
}
public static MultiFormatResult MatchFormat => new MultiFormatResult();
public string GetCurrentFormatFrom(HttpContext context)
{
return context.GetRouteValue("_format") as string ?? "html";
}
public MultiFormatResult AddView(Func<ViewResult> view)
{
return AddFormat("html", view);
}
public MultiFormatResult AddView()
{
return AddView(() => new ViewResult());
}
public MultiFormatResult AddFormat(string format, Func<object> func)
{
this[format] = func;
return this;
}
public MultiFormatResult WrapFormat<T>(string format, Func<object> func) where T : class, IActionResult
{
object Wrapper() => typeof(T).GetConstructor(new[] {typeof(object)}).Invoke(new[] {func.Invoke()});
this[format] = Wrapper;
return this;
}
public Task ExecuteResultAsync(ActionContext context)
{
return GetResultFrom(context.HttpContext)
.ExecuteResultAsync(context);
}
public bool IsFound(HttpContext context)
{
var routeValues = context.GetRouteData().Values.Values
.Select(x => x.ToString()).ToList();
var urlSegments = context.Request.Path.Value.Split(
new[] {'.', '/'},
StringSplitOptions.RemoveEmptyEntries);
return routeValues.SequenceEqual(urlSegments);
}
public IActionResult GetResultFrom(HttpContext context)
{
if (!IsFound(context))
{
return new NotFoundResult();
}
if (!_formats.TryGetValue(GetCurrentFormatFrom(context), out var format))
{
return new NotFoundResult();
}
var result = format.Invoke();
if (result is IActionResult actionResult)
{
return actionResult;
}
return new ObjectResult(result);
}
}
}
@mohammed-io
Copy link
Author

mohammed-io commented Jul 13, 2019

To use this solution,

First import it in the top of your file using static MohammedIo.MultiFormat.MultiFormatResult;

Then in your action:

    public IActionResult Index()
    {
        return MatchFormat
            .AddView(View)
            .AddFormat("json", () => new {Id = 1, Name = "ABC"});
       // OR
        return new MultiFormatResult
          {
              ["html"] = View,
              ["json"] = () => new {Id = 1, Name = "ABC"},
          };
    }

While MatchFormat is the static property of the class above.

Then in your routes add an optional.{_format?} segment to the route template so it will be parsed, like the example below:

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}.{_format?}/{id?}");
    });

That's it! now you have a Multi-Format response like the Rails framework.

Now visit that endpoint, /Index (or /Index.html) and /Index.json now return different formats.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment