WebAPI címkéhez tartozó bejegyzések

WebAPI dokumentáció kibővítése

Írtam már arról, hogy a Microsoft.AspNet.WebApi.HelpPage NuGet csomaggal milyen könnyen generálhatunk dokumentációt a WebAPI-nkhoz. Mint minden készen kapott megoldással, ezzel is akkor kezdődnek a nehézségek, amikor az ember olyasmit szeretne, amire helyből nincs felkészítve. Szerencsére ebben az esetben a teljes forráskód rendelkezésre áll, így viszonylag egyszerűen testreszabhatjuk a működést és a megjelenést.

Példaként tegyük fel, hogy az API-nk úgy épül fel, hogy minden action hiba esetén a hozzá tartozó enum valamelyik elemét adja vissza hibakódnak a hozzá tartozó hibaüzenettel együtt, és a feladat az, hogy ezek a hibakódok megjelenjenek a dokumentációnkban.

Egy saját attribútummal – amit itt ErrorCodeType-nak hívtam, –  könnyen hozzákapcsolhatjuk a hibakódokat tartalmazó enumot (itt MyErrorCodeEnum) az actionhöz:

[ErrorCodeType(typeof(MyErrorCodeEnum))]

Ezt az információt a következő lépésekkel tehetjük bele a dokumentációba:

1. A Models mappába hozzunk létre egy új osztályt, ami majd egy konkrét hibaág leírására lesz alkalmas, hívjuk ezt ErrorCodeDescription-nek:

public class ErrorCodeDescription
{
  public int Code { get; set; }
  public string Message { get; set; }
}

 

2. A HelpPageApiModel.cs fájl írja le azt a view-modellt, ami alapján a dokumentáció oldal megjelenik. Ebbe az osztályba vegyünk fel egy új tulajdonságot, ami az összes hibalehetőséget fogja tartalmazni egy listában:

public List<ErrorCodeDescription> ErrorResponses { get; set; }

 

3. A HelpPageConfigurationExtensions.cs fájlban található az a GenerateApiModel metódus, ami felépíti a HelpPageApiModel típusú apiModel nevű változóban található view-modellt. Itt a meglévőek mintájára készíthetünk egy saját metódust, ami kiolvassa a csatolt attribútumot, és az alapján felépíti az ErrorResponses listát, például így:

private static void GenerateErrorResponses(HelpPageApiModel apiModel)
{
  ErrorCodeTypeAttribute attribute = apiModel.ApiDescription.ActionDescriptor
    .GetCustomAttributes<ErrorCodeTypeAttribute>().FirstOrDefault();
  Type enumType = attribute.ErrorCodeEnumType;
  string[] names = Enum.GetNames(attribute.ErrorCodeEnumType);

  foreach (string name in names)
  {
    apiModel.ErrorResponses.Add(new ErrorCodeDescription
    {
      Code = (int) Enum.Parse(enumType, name),
      Message = "TODO"
    });
  }
}

 

4. A modell kirenderelését a Views/Help/DisplayTemplates/HelpPageApiModel.cshtml nézet végzi. A meglévő kódok mintájára ide felvehetjük az alábbi blokkot, ami az ErrorResponses lista megjelenítését az ErrorResponses partial view-ra bízza:

@if (Model.ErrorResponses.Any()) 
{ 
  <h3>Error Responses</h3> 
  @Html.DisplayFor(m => m.ErrorResponses, "ErrorResponses") 
}

 

5. Természetesen létre kell hoznunk ugyanebben a mappában egy ErrorResponses.cshtml fájlt, ami @model-ként egy List<ErrorCodeDescription> példányt fog kapni, amit utána úgy jelenítünk meg a weboldalon, ahogy csak szeretnénk.

 

Ez elsőre sok lépésnek tűnik, de valójában egészen logikus, érdemes megbarátkozni vele.

 

Technorati-címkék: ,

WebAPI dokumentáció generálása több szerelvényből

A Microsoft.AspNet.WebApi.HelpPage NuGet csomag segítségével nagyon könnyen generálhatunk egészen barátságos dokumentációt a WebAPI-nkhoz. A megoldás alapja, hogy a WebAPI projekt kommentjeiből a C# fordítót felhasználva XML fájlt készítünk, amit aztán egy MVC controller felolvas, és barátságos HTML oldalakká fordít.

A problémák akkor jönnek, ha a a REST API publikus interfészén olyan típusok találhatóak, amik nem a WebAPI projektben vannak, hanem egy másik DLL-ben, ugyanis a másik DLL – tipikusan library – kommentjeiből generált XML fájl ugyan bekerül az API bin mappájába, de a webdeploy már nem viszi magával, tehát futási időben nem lesz ott a megfelelő helyen.

A probléma két lépésben orvosolható:

  1. A library XML fájljának bele kell kerülnie a webdeploy csomagba.
  2. A controllert át kell írni, hogy több XML fájllal is működjön.

 

A webdeploy csomag kiegészítése

A webdeploy csomag úgy készül, hogy fordítás után összemásolódik az összes fájl egy mappába, majd ennek a mappának a tartalmából áll össze a csomag. Mivel ezt a folyamatot az MSBuild vezérli, kis trükkel újabb fájlt is be tudunk tenni ebbe az ideiglenes mappába. Mindössze a WebAPI projekthez tartozó .csproj fájlt kell kiegészítenünk:

<Target Name="CopyAdditionalFiles">
  <Message Text="Copy additional files..." Importance="high"/>

  <ItemGroup>
    <_CustomFiles Include="$(ProjectDir)bin\MyLib.xml" />
    <FilesForPackagingFromProject Include="%(_CustomFiles.Identity)">
      <DestinationRelativePath>bin\%(RecursiveDir)%(Filename)%(Extension)</DestinationRelativePath>
    </FilesForPackagingFromProject>
  </ItemGroup>
</Target>

<PropertyGroup>
  <CopyAllFilesToSingleFolderForMsdeployDependsOn>
    CopyAdditionalFiles;
    $(CopyAllFilesToSingleFolderForMsdeployDependsOn);
  </CopyAllFilesToSingleFolderForMsdeployDependsOn>
</PropertyGroup>

A fenti módosítás Visual Studio 2012-től kezdve működik, a korábbi verziókhoz képest ugyanis átnevezték a CopyAllFilesToSingleFolderForMsdeployDependsOn elemet.

 

Dokumentáció generálása több XML fájlból

A fent említett NuGet csomag egyetlen XML fájlból tud dokumentációt előállítani. Ha azt szeretnénk, hogy több fájllal is megbirkózzon, akkor módosítanunk kell az XmlDocumentationProvider osztály konstruktorát:

public XmlDocumentationProvider(IEnumerable<string> documentPaths)
{
  if (documentPaths == null)
  {
    throw new ArgumentNullException("documentPaths");
  }

  XDocument finalDoc = null;
  foreach (string documentPath in documentPaths)
  {
    if (finalDoc == null)
    {
      finalDoc = XDocument.Load(File.OpenRead(documentPath));
    }
    else
    {
      XDocument additionalDoc = XDocument.Load(File.OpenRead(documentPath));
      finalDoc.Root.XPathSelectElement("/doc/members")
        .Add(additionalDoc.Root.XPathSelectElement("/doc/members").Elements());
    }
  }

  _documentNavigator = finalDoc.CreateNavigator();
}

A módosítás eredményeként a konstruktornak már több fájl útvonalát kell átadni a korábbi egy helyett. Az elsőt ugyanúgy fogja betölteni, mint korábban, a többit pedig utána fogja fűzni a memóriában. A végeredmény egyetlen nagy XML dokumentum, így a NuGet csomag többi részét nem is kell módosítanunk.

Természetesen a HelpPageConfig.cs-ben már tömb paraméterrel kell meghívnunk a konstruktort, például:

string[] documentPaths = 
{
  HttpContext.Current.Server.MapPath("~/bin/MyAPI.xml"),
  HttpContext.Current.Server.MapPath("~/bin/MyLib.xml")
};
config.SetDocumentationProvider(new XmlDocumentationProvider(documentPaths));

 

Technorati-címkék: ,,

JSON vagy nem JSON, ez itt a kérdés

Amikor egy REST API-hoz készítünk unit tesztet könnyen előfordulhat, hogy meg kell vizsgálnunk, hogy a szervertől kapott válasz olyan formában van-e, mint amiben kértük. Például egy stringről el kell döntenünk, vagy korrekt JSON-t tartalmaz-e.

Sok olyan megoldást lehet találni a neten, ami azt mondja, hogy elég, ha ellenőrizzük, hogy a kapott string < vagy { karakterrel kezdődik-e, hiszen a JavaScript Object Notation mindenképpen objektumokról vagy tömbökről szól. Ezzel a módszerrel nem csak az a baj, hogy egyetlen karakterből aligha tudjuk megmondani, hogy érvényes JSON stringről van-e szó, hanem az is, hogy érvényes lehet az a string is, ami nem tömböt vagy objektumot, hanem csak egy egyszerű értéket tartalmaz. Ami pedig a json.org szerint lehet:

json-value

A fapados karakter vizsgálgatás helyett szükségünk lenne egy korrektebb IsValidJson függvényre. Sajnos ilyet sehol nem találtam készen, de a Newtonsoft JSON.NET könyvtárral sikerült így megoldani a problémát:

try
{
    JToken.Parse(input);
    return true;
}
catch (JsonReaderException)
{
    return false;
}

Van jobb ötlete valakinek?

 

Technorati-címkék: ,,