hendryluk / cormo

.NET Application Framework based on Spring and Java EE CDI
MIT License
5 stars 4 forks source link

Cormo.Web: Injecting responses #10

Open hendryluk opened 9 years ago

hendryluk commented 9 years ago

Currently this is how you set response details (e.g. headers) with Cormo (or rather, WebApi):

[RestController]
public class MyController
{
    [Route, HttpGet]
    public HttpResponseMessage DoSomething()
    {
        /* ... */
        var response = Response.Ok(something);
        response.Headers.LastModified = content.LastModified;
        return response;
    }
}

That's the same way in JBoss. But additionally, according to the CDI spec (or rather, JBoss Seam), there's an alternative way may be better for flattening your code, which is by injecting your HttpResponseMessage to components. The benefit being that setting headers can be done by any component, no longer necessarily only by your controller (or UI layer in general). Ref: http://docs.jboss.org/seam/3/latest/reference/en-US/html_single/#injectablerefs.http_servlet_reponse E.g.

public class MyShoppingCart
{
   [Inject] HttpResponseMessage _response;
   public void DoSomething()
   {
       /* ... */
       response.Headers.LastModified = items.LastModified;
   }
}

This will add the header to the response that's eventually returned by web-api, even though the controller code is totally oblivious to it:

[RestController]
public class MyController
{
    [Route, HttpGet]
    public string DoSomething()
    {
        /* ... */
        _cart.DoSomething();
        return something;
    }
}

This helps reducing vertical layering in your application and simplifies some code by removing some unnecessary patterns. PS: I'll leave it to you to decide whether that's a "good practice" to mix http concerns into your components, but some components are inherently web-coupled, such as identities, security (e.g. anti-csrf), captcha, and web-analytics functionalities, which are all commonly designed as components/interceptors/decorators. CDI provides a decoupled way to wire those up without introducing noise in your controller.

jimmyp commented 9 years ago

Just this morning I was wrestling with how to return a NotFound response code when I didn't find an entity inside a domain service... I'm not sure I like the mixing of concerns, but I def see the value.

hendryluk commented 9 years ago

Ah i see. Yea there are 4 ways you could do that in Cormo (pulled straight out of jax-rs playbook). Not very well documented (sorry about that).

Option 1

[Route, HttpGet, HttpStatusCode(HttpStatusCode.NotFound)]
public object Something()
{
}

That's probably not very appropriate for not-found, but is certainly useful for redirects, ok, accepted, etc.

Option 2

[Route, HttpGet]
public HttpMessageResponse Something()
{
    return Response.NotFound();
}

Which is more appropriate for not-found case.

Option 3

(Note that this, exception-handling, is currently not yet implemented in cormo, but it's part of CDI spec). Issue: https://github.com/hendryluk/cormo/issues/12

[Route, HttpGet]
public object Something()
{
    throw MyNotFoundException();
}

public void OnNotFoundException(
     [HandlesException] MyNotFoundException, 
     HttpResponseMessage response)
{
   response.StatusCode = HttpStatusCode.NotFound;
}

Which could be shortened into:

[HttpStatusCode(HttpStatusCode.NotFound)]
public void OnNotFoundException([HandlesException] MyNotFoundException)
{
}

Then you can throw that exception from anywhere within your application. Note that this is part of standard core CDI spec, not web specific. So you declare this exception-handling methods on any components for any purpose, not just for web and status-code stuff.

Option 4

Again, not yet implemented. Similar to option#3, but instead of exception-handling method, you got:

[HttpStatusCode(HttpStatusCode.NotFound)]
public class MyNotFoundException: Exception
{
}

And lastly

Of course the good old web-api way:

public class MyController: ApiController
{
   [Route, HttpGet]
   public IHttpActionResult Something()
   {
      return NotFound();
   }
}
jimmyp commented 9 years ago

I was thinking of implementing something like option 3 myself. Nice.

hendryluk commented 9 years ago

I've created an issue for implementing CDI's exception-handling: https://github.com/hendryluk/cormo/issues/12