1
Vote

Injecting custom IActionResultConverter implementations through the DependencyResolver as a service or based on rules

description

@kichalla and I have discussed this issue also on http://aspnetwebstack.codeplex.com/discussions/399311. Today, you need to jump a lot of hoops to provide a custom IActionResultConverter implementation because the HttpActionDescriptor has a deep knowladge about the ActionResultConverters (see the virtual ResultConverter property of HttpActionDescriptor).

It would open up lots of flexibilities if custom IActionResultConverter implementations can be injected through the DI as a service or based on rules (just like as it is with HttpParamaterBindings today).

comments

marcind wrote Oct 16, 2012 at 4:43 PM

What type of flexibility are you looking for? I agree that we should make it easier to replace these instances, but I'm curious how you would use the power. Perhaps we could build some of that into the framework.

tugberk wrote Oct 16, 2012 at 6:02 PM

Hi marcind,

Here is a scenario which made me realize that I needed a custom action converter. When we want to create new resource, we usually have a controller action which is smilar to the below one (gist URL: https://gist.github.com/3900845#file_people_controller_with_http_response_message.cs):

public class PersonRequestModel {
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
}

public class PersonDto {
public Guid Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
}

public class PeopleController : ApiController {
// POST /api/people
// { "Name" : "Foo", "Surname": "Bar", "Age": 25 }
public HttpResponseMessage PostBar(
    PersonRequestModel requestModel) {

    // persist the person object here
    // assume that we have done that and get the 
    // Id as well
    var personDto = new PersonDto {
        Id = Guid.NewGuid(),
        Name = requestModel.Name,
        Surname = requestModel.Surname,
        Age = requestModel.Age
    };

    var response = Request.CreateResponse(HttpStatusCode.Created, personDto);
    response.Headers.Location = new Uri(Url.Link(
        "DefaultHttpRoute", new { id = personDto.Id }));

    return response;

    // Response should be as below:
    // HTTP/1.1 201 Created
    // Location: http://localhost:34616/api/people/738bfbbe-3e51-452f-8689-a5d6c78dd81a
    // Other headers goes here...
    //
    // { "Id": "738bfbbe-3e51-452f-8689-a5d6c78dd81a", "Name" : "Foo", "Surname": "Bar", "Age": 25 }
}
}

This works fine. But notice that I needed to create the response by myself and I user Request.CreateResponse extension method to run the conneg, too. I also append the Link to the location header. I see two overheads here:

1- If I have a big application and have this type of actions a lot, I would be repeating myself. Sure, I could just create a static helper method or something else to do this job easily for me but my second reason will make this a undesirable option.

2- I had to use the Request property of the ApiController inside my controller. This is 99% OK for me since I don't unit test my controllers. Instead, I handle the test of my controllers through the integration testing. Even if I need to unit test them, it's easy fairly cheap to create new HttpRequestMessage instances. However, it still makes me uncomfortable to do that because a result converter should be the one which dees this job for me.

So, I come up with the below solution to this problem (if you call this a problem :)) (gist URL: https://gist.github.com/3900845#file_people_controller_with_http_created_result.cs):

public class HttpCreatedResult<TDto> {
public TDto Result { get; set; }
public string Link { get; set; }
}

public class PersonRequestModel {
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
}

public class PersonDto {
public Guid Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
}

public class PeopleController : ApiController {
// POST /api/people
// { "Name" : "Foo", "Surname": "Bar", "Age": 25 }
public HttpCreatedResult<PersonDto> PostBar(
    PersonRequestModel requestModel) {

    // persist the person object here
    // assume that we have done that and get the 
    // Id as well
        var personDto = new PersonDto { 
            Id = Guid.NewGuid(),
            Name = requestModel.Name,
            Surname = requestModel.Surname,
            Age = requestModel.Age
        };

    return new HttpCreatedResult<PersonDto> { 
        Result = personDto,
        Link = Url.Link(
            "DefaultHttpRoute", new { id = personDto.Id })
    };

    // Response should be as below:
    // HTTP/1.1 201 Created
    // Location: http://localhost:34616/api/people/738bfbbe-3e51-452f-8689-a5d6c78dd81a
    // Other headers goes here...
    //
    // { "Id": "738bfbbe-3e51-452f-8689-a5d6c78dd81a", "Name" : "Foo", "Surname": "Bar", "Age": 25 }
}
}

Assuming that we have a ResultConverter which can convert HttpCreatedResult<TDto> types, this is much more cleaner IMHO. We can think of a few other scenarios as well (such as being able to directly return HttpStatusCode).

What do you think?

tugberk wrote Oct 21, 2012 at 11:39 AM

One other place where I would like to insject custom IActionResultConverter impl. would be to support "Prefer: return-minimal" which is not an official standard yet. I can still do that with a filter or message handler cleanly but it wouldn't be as efficient as handling it with a custom IActionResultConverter impl.

HongmeiG wrote Apr 6 at 5:47 AM

This similar to the IHttpActionResult idea on the ApiController.