Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Refactor content negotiation code into a service #6998

Merged
merged 2 commits into from
Oct 31, 2017

Conversation

rynowak
Copy link
Member

@rynowak rynowak commented Oct 26, 2017

This is a refactor in anticipation of using this logic in some other
places

@khellang
Copy link
Contributor

YES! 😍


namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public abstract class OutputFormatterSelector

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of an abstract class instead of an interface?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has a better set of tradeoffs for something like this (IMO).

With an abstract class we can easily add new functionality or overloads without breaking anything. When something is implementation-ey, the requirements frequently change. We'd do something like add a new API, obsolete the old one, and then remove it in the next major release. You can't do that with an interface, you have to make a new interface.

Interfaces are better when the contract is minimal, the requirements seldom change, or when we expect lots of implementations.


I'm open to feedback if you think this is the wrong direction

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a clear preference in this case. An abstract class with only a single abstract method seems a bit strange, but I understand that the class is better than the interface for future changeability.

Interfaces are better when the contract is minimal, the requirements seldom change

I kind of expect a content negotiation interface to be minimal and rarely changing. Ideally, we could have something like this:

public interface IContentNegotiator
{
  IOutputFormatter SelectFormatter(IEnumerable<IOutputFormatter> formatters, MediaTypeCollection mediaTypes);
}

But then the selection logic would not take into account if the result object can be serialized by the selected output formatter and I guess that's important too. :-)

BTW I got to this PR, because I wanted to do content negotiation in an exception handling middleware. Currently the easiest way of doing it is calling the SelectFormatter by reflection. This PR would allow me to do that without any reflection hack, so as far as I'm concerned, this is perfect.

private readonly bool _respectBrowserAcceptHeader;
private readonly bool _returnHttpNotAcceptable;

public DefaultOutputFormatterSelector(ILoggerFactory loggerFactory, IOptions<MvcOptions> options)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoggerFactory after Options (to keep it consistent with most of our other API)

formatters = _formatters;

// Complain about MvcOptions.OutputFormatters only if the result has an empty Formatters.
Debug.Assert(formatters != null, "MvcOptions.OutputFormatters cannot be null.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this won't be null (since you initialize this)

}
}

result.Sort((left, right) => left.Quality > right.Quality ? -1 : (left.Quality == right.Quality ? 0 : 1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated: should we cache this delegate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure - it will be cached already since it doesn't capture, but making it explicitly cached makes it harder to break

/// <param name="writerFactory">The <see cref="IHttpResponseStreamWriterFactory"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public ObjectResultExecutor(
IOptions<MvcOptions> options,
OutputFormatterSelector formatterSelector,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's our level of concern for breaking change here? Particularly since this is in the .Infrastructure namespace?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type was pubternal in 2.0 so there's no breaking change


namespace Microsoft.AspNetCore.Mvc.Infrastructure
{
public abstract class OutputFormatterSelector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need some docs for this + the method

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

This is a refactor in anticipation of using this logic in some other
places
@rynowak rynowak force-pushed the rynowak/formatter-service branch from 2895f44 to 1e82aab Compare October 31, 2017 21:27
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants