Rails' respond_to in ASP.NET MVC nl

Door creator1988 op maandag 17 januari 2011 14:15 - Reacties (7)
Categorie: Frontend, Views: 4.154

In Ruby on Rails is het mogelijk om met respond_to een actie beschikbaar te maken in andere formaten dan HTML met één regel. Pretty neat, omdat je je code zo zonder moeite via verschillende interfaces kan gebruiken.

Ruby:
1
2
3
4
5
respond_to do |format|
  format.html
  format.xml  { render :xml => @huis }
  format.json { render :json => @huis }
end


In ASP.NET MVC 2 is er niet standaard zo'n oplossing, maar doordat MVC zo pluggable is is deze wel eenvoudig toe te voegen.

Models en Views
Hierbij introduceer ik jullie tot de nieuwe site 'fudna'. Ze tonen huizen en hebben hiervoor de volgende MVC structuur.
http://www.100procentjan.nl/tweakers/fudnamvc.png
De actie 'Index' op de 'HuisController' ziet er zo uit:

C#:
1
2
3
4
5
6
public ActionResult Index(int id)
{
    var model = FudnaDao.GetHuis(id);

    return View(model);
}


Wat resulteert in de weergave van dit vernieuwende concept:
http://www.100procentjan.nl/tweakers/fudnaview.png

En nu andere formaten
Allereerst breiden we de standaard routing uit in Global.asax. Naast de 'standaard' regel voegen we een rule toe die extensies accepteert.

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    // dit is nieuw
    routes.MapRoute(
        "DefaultWithExtension",
        "{controller}/{action}/{id}.{format}",
        new { controller = "Home", action = "Index" } // Parameter defaults
    );

    // dit niet meer
    routes.MapRoute(
        "Default",
        "{controller}/{action}/{id}",
        new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
    );
}


Om aan te geven welke formaten we ondersteunen maken we gebruik van ActionFilterAttributes. Mooi hieraan is dat we het resultaat van elke actie kunnen overschrijven in deze filters. Het RespondTo filter zou er ongeveer zo uit zien:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class RespondTo : ActionFilterAttribute 
{
    private Format[] _formats;
    public RespondTo(params Format[] formats)
    {
        _formats = formats;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // zoek het model op
        var model = ((ViewResult) filterContext.Result).ViewData.Model;

        // hebben we een format parameter?
        object format;
        filterContext.RouteData.Values.TryGetValue("format",  out format);

        // zoja, dan kijk naar de waarde
        switch(format as string)
        {
            // als json niet ondersteunt; dan return
            case "json":
                if (!_formats.Any(f => f == Format.Json)) return;

                // transformeer het model naar Json
                // en overschrijf het oude Result
                filterContext.Result = new JsonResult { Data = model, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
                break;

            case "xml":
                if (!_formats.Any(f => f == Format.Xml)) return;

                // same same voor XML. Maar dat is lastiger in een one-liner
                using(MemoryStream ms = new MemoryStream()) // simpele serialization
                    using(TextWriter tw = new StreamWriter(ms))
                    {
                        new XmlSerializer(model.GetType()).Serialize(tw, model);
                        ms.Seek(0, SeekOrigin.Begin);
                        // hier zetten we de nieuwe content naar de XML
                        filterContext.Result = new ContentResult { ContentType = "text/xml", Content = new StreamReader(ms).ReadToEnd() };
                    }

                break;
        }
    }
}

public enum Format
{
    Xml,
    Json
}



Actie aanpassen
Aan de bestaande actie hoeven we nu niets meer aan te passen. We zetten er enkel een nieuw attribuut op:

C#:
1
2
3
4
5
6
7
[RespondTo(Format.Xml, Format.Json)]
public ActionResult Index(int id)
{
    var model = FudnaDao.GetHuis(id);

    return View(model);
}



Resultaat
En dat was het al. Wanneer we nu '.xml' of '.json' toevoegen aan de URL krijgen we de data in dat formaat binnen!
http://www.100procentjan.nl/tweakers/fudnaxml.png

http://www.100procentjan.nl/tweakers/fudnajson.png

Volgende: Waarom je nooit mag toegeven dat je Ruby on Rails hebt bestudeerd 01-'11 Waarom je nooit mag toegeven dat je Ruby on Rails hebt bestudeerd
Volgende: Solr, deel 1: Introductie tot faceted search 01-'11 Solr, deel 1: Introductie tot faceted search

Reacties


Door Tweakers user CodeCaster, maandag 17 januari 2011 14:33

Waarom switch je op string terwijl je daar juist zo'n mooie enum voor hebt waarmee je kan parsen? Het lijkt voor mijn niet-Linq-getrainde oog alsof je iets vergelijkbaars doet in je case, maar dit lijkt mij leesbaarder:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try
{
  filterContext.RouteData.Values.TryGetValue("format",  out formatString); 
  Format format = Enum.Parse(typeOf(Format), formatString);

  switch (format)
  {
    case Xml:
      // Do something
    break;
    case Json:
      // Do something
    break;
  }
}
catch (ArgumentException)
{
  // Geen herkend format
}


:)

Door Tweakers user creator1988, maandag 17 januari 2011 14:40

CodeCaster schreef op maandag 17 januari 2011 @ 14:33:
Waarom switch je op string terwijl je daar juist zo'n mooie enum voor hebt waarmee je kan parsen? Het lijkt voor mijn niet-Linq-getrainde oog alsof je iets vergelijkbaars doet in je case, maar dit lijkt mij leesbaarder:
Nou, de strings zijn voor de extensie, en de extensie is nu toevallig gelijk aan de waarde van de enum. Met het los parsen van de strings kan je de extensie ".pietje" JSON laten uitspugen.

Overigens zou ik voor productiecode eerder iets doen als:

C#:
1
2
3
4
public enum Format {
      [Extension("xml")]
      Xml
}


en dan op basis van die attributes kijken of er een match is :)

[Reactie gewijzigd op maandag 17 januari 2011 14:40]


Door Tweakers user YopY, maandag 17 januari 2011 15:02

Nu moet je voordat je dit toe gaat passen op je sites wel eerst bedenken of je alternatieve uitvoersmethodes wilt aanbieden. Effectief maak je met deze techniek van je website een webservice, waar allerlei software mee kan werken. Is natuurlijk goed als je bijvoorbeeld een Fudna 'app' wilt maken voor telefoons waarmee je dezelfde data wilt benaderen, maar weer niet als je je content wat wilt bewaken. Natuurlijk, HTML is ook eenvoudig te crawlen, maar XML en/of JSON zijn eenvoudiger binnen te slepen.

Door Tweakers user creator1988, maandag 17 januari 2011 15:21

YopY schreef op maandag 17 januari 2011 @ 15:02:
Nu moet je voordat je dit toe gaat passen op je sites wel eerst bedenken of je alternatieve uitvoersmethodes wilt aanbieden. Effectief maak je met deze techniek van je website een webservice, waar allerlei software mee kan werken. Is natuurlijk goed als je bijvoorbeeld een Fudna 'app' wilt maken voor telefoons waarmee je dezelfde data wilt benaderen, maar weer niet als je je content wat wilt bewaken. Natuurlijk, HTML is ook eenvoudig te crawlen, maar XML en/of JSON zijn eenvoudiger binnen te slepen.
Klopt, maar je hebt hier nu wel handvatten om ook authenticatie toe te voegen. Bijvoorbeeld alleen de normale weergave als je anoniem bent, en ook JSON / XML als je een API Key meegeeft.

Door Tweakers user Hobbles, dinsdag 18 januari 2011 00:29

Van ASP .NET MVC 3 is intussen ook een final release uitgekomen met een andere View engine (Razor). Misschien dat daar ook een soortgelijke feature inzit maar dat weet ik niet met zekerheid.

In ieder geval een mooi stukje over de uitbreidbaarheid van ASP .NET MVC. Een framework dat naar mijn idee jammer genoeg veel te weinig aandacht geniet van de gemiddelde .NET ontwikkelaars.

Door Tweakers user creator1988, dinsdag 18 januari 2011 10:39

Hobbles schreef op dinsdag 18 januari 2011 @ 00:29:
Van ASP .NET MVC 3 is intussen ook een final release uitgekomen met een andere View engine (Razor). Misschien dat daar ook een soortgelijke feature inzit maar dat weet ik niet met zekerheid.

In ieder geval een mooi stukje over de uitbreidbaarheid van ASP .NET MVC. Een framework dat naar mijn idee jammer genoeg veel te weinig aandacht geniet van de gemiddelde .NET ontwikkelaars.
Thx, had nog niet eens door dat MVC 3 al final was :) . Ik moet eerlijk zeggen dat ik pas nu ik in RoR aan het duiken ben, ik de echte kracht van het MVC pattern begin te zien. Daar ligt voor Microsoft nog wel een taak an sich.

Door Gadgets, woensdag 16 februari 2011 15:24

Als .NET / MVC ontwikkelaar (freelance) denk ik dat ik dit zou oplossen door te kijken naar de accept property in de request header. Als er een mediatype */* wordt gevraagd zou ik de normale view teruggeven (vaak HTML). Is er een specifiek mediatype in de accept header meegegeven dan zou ik dit type retouneren b.v: 'application/json'. Het is simpel te bouwen zoals jouw voorbeeld met een actionfilter.

Succes!

Reageren is niet meer mogelijk