gil.r
gil.r

Reputation: 13

C# REST API, Children of model should be of same type, and routes must be recursively defined

Hi I am having an architectural problem with .NET Core

I have a controller called SContent with this route ->

[Route("api/content")]

now what I want to do is to create a recursive Route for any of the following cases:

/api/content/{id}/children/{id2}
/api/content/{id}/children/{id2}/children
/api/content/{id}/children/{id2}/children/{id3}

and so on and so on...

is it possible to do something like that? - children are of the same type of parent - {id(N)} should always be a child of {id(N-1)}

Thanks

Upvotes: 1

Views: 275

Answers (1)

itminus
itminus

Reputation: 25380

I'm afraid that there is no built-in route that can meet your needs . However, writing a custom middleware is easy .

short answer :

  1. write a predicate which will set Context.Items["Ids"] and Context.Items["WantChildren"]
  2. pass the predicate to a MapWhen() method .
  3. write a middleware that will deal with logic to show content or get it's children according to Context.Items["Ids"] and Context.Items["WantChildren"].

Quick and Dirty Demo

Here's a quick and dirty demo :

app.MapWhen(
    context =>{
        var path=context.Request.Path.ToString().ToLower();
        if (path.EndsWith("/")) {
            path = path.Substring(0, path.Length-1);
        }
        if (!path.StartsWith("/api/content")) {
            return false;
        }
        var ids = new List<int>();
        var wantChildren = false;
        var match= Regex.Match(path,"/(?<id>\\d+)(?<children>/children)?");
        while (match.Success) {
            var id = Convert.ToInt32(match.Groups["id"].Value);  // todo: if throws an exception  , ...
            wantChildren= !String.IsNullOrEmpty(match.Groups["children"].Value);
            ids.Add(id);
            match = match.NextMatch();
        }
        context.Items["Ids"] = ids;
        context.Items["WantChildren"] = wantChildren;
        return true;
    },
    appBuilder => {
        appBuilder.Run(async context =>{
            var ids = (List<int>)(context.Items["Ids"]);
            var wantChildren = (bool)(context.Items["WantChildren"]);

            // just a demo 
            // the code below should be replaced with those that you do with id list and whether you should display children
            foreach (var id in ids) {
                await context.Response.WriteAsync(id.ToString());
                await context.Response.WriteAsync(",");
            }
            await context.Response.WriteAsync(wantChildren.ToString());
        });
    }
);

here's a screenshot that works test-recursive-path

Futher Refactoring

For better maintence , you can extract Ids and WantChildren to a single Class , for intance , ContentChildrenContext :

public class  ContentChildrenContext{
    public List<int> Ids {get;set;}
    public bool WantChildren{get;set;}
}

you can also make abstraction around the middleware itself , for example, create a factory method which returns a RequestDelegate that can be used easily with app.Run():

Func<Func<ContentChildrenContext,Task>,RequestDelegate> CreateContentChildrenMiddleware(Func<ContentChildrenContext,Task> action){
    return async content =>{
        var ccc= (ContentChildrenContext)(context.Items["ContentChildrenContext"]);
        await action(ccc);
    };
}

Best Regards .

Upvotes: 1

Related Questions