问题
In short, I have two database tables: Languages and Frameworks. The important thing is that there is one-to-many relation between the tables(one language has many frameworks). I am designing a RESTful(WebAPI2) service to consume the information from these tables. I am using the repository pattern. Using EF, I've made a navigational property for the language to reach its frameworks.
However, how should that be implemented in the repositories. Is it correct to return a framework collection in the Language repository? I want to reach the frameworks of a language using the same WebAPI controller because it gives a more simple route and I'm not sure if that is a correct way to do it at all.
public class LanguagesController : ApiController
{
private readonly IProgrammingLanguageRepository languages;
public LanguagesController() : this(new ProgrammingLanguageRepository(new CVSystemDbContext()))
{
}
public LanguagesController(ProgrammingLanguageRepository languagesRepository)
{
this.languages = languagesRepository;
}
[HttpGet]
[Route("api/languages")]
public IHttpActionResult GetAll()
{
return this.Ok(this.languages.GetAll());
}
[HttpGet]
[Route("api/languages/{id:int}")]
public IHttpActionResult GetById(int id)
{
return this.Ok(this.languages.GetById(id));
}
[HttpGet]
[Route("api/languages/{id:int}/frameworks")]
public IHttpActionResult GetByLanguage(int id)
{
----
}
}
In the other hand, if I implement it in the framework repository(either by using the navigation property from the other table in the context or by scanning by id), I should use some nasty routing in the framework WebAPI controller(something like "api/frameworks/bylanguage/{id}") which again doesn't seem right.
回答1:
In my opinion, for a RESTful service to be as clean as possible it is the best approach to define api controllers on a business entity basis. That is, you should define a controller to do your CRUD operations on languages
and frameworks
separately, like so:
public class LanguagesController : ApiController
{
public IHttpActionResult Get(int id)
{
// logic to query and return a language by id
}
public IHttpActionResult GetAll()
{
// logic to query all (and possibly paginate) all the languages
}
}
public class FrameworksController : ApiController
{
public IHttpActionResult Get(int id)
{
// logic to query and return a framework by id
}
public IHttpActionResult GetByLanguage(int id)
{
// logic to return the frameworks of a specific language
}
public IHttpActionResult GetAll()
{
// logic to query all (and possibly paginate) all the frameworks
}
}
The point here is that for a RESTful service, in my opinion, it is the best to not assume anything about how the data returned by them are consumed. That is, you should not assume that everywhere the languages are displayed the frameworks are displayed as well. For example, imagine that some client lists the languages in a tabular manner. It would make a pretty messy UI to include the frameworks there as well. It is better to let the user pick a language and display the details separately, for example, in a modal window. The whole point of a RESTful service is, afterall, to supply data as granually as possible and not to tailor it to your specific views. They are meant to serve data to all kinds of HTTP-capable devices and software - including browsers and smartphone apps. If you want to just serve some data tailored to your views using AJAX calls, it is better to just define action methods on regular MVC controllers that respond to ajax calls and return a JsonResult
.
Also one thing I'd like to point out here is that you should not return DB entites in your services. I'll mention two+one reasons to this:
- Security: returning DB entities without any control means that every time you modify your data model, these changes are reflected in the data sent back to the clients. This is because the serializer that transforms your data into the demanded format is not aware of what data to include - unless you add attributes to it, which you shouldn't do in this case because the project that contains your entities should not rely on web api-specific assemblies. So if you modify your entites so that some of them contains some sort of sensitive data, this data is included in the response as well. This might be a thing not to worry about in your specific domain, but you should keep this in mind.
- Domain traversal: the serializer that transforms your data - unless instructed otherwise by using attributes which is problematic as I explained in the previous bulletpoint - simply transforms every property it finds to the demanded format, say to JSON, recursively. Generally, you will have way more entites than described in your question - users (containing sensitive data, such as password hashes), administrative data and whatever you can think of. It is very uncommon for a data model to be not connected - that is, any entity can be reached from any other entity by traversing one or more navigation property. So if your serializer is not told exactly which data to include and transform, it just recursively includes any connected entites. Because of the data model generally being connected, this simply means every record of every table gets included and this leads to a stack overflow exception or simply a too large response message.
- Security+domain traversal combined: the first and second issue implies that if your db has few enough data to fit in a single response and they all just get serialized, any sensitive data (for example, user data, password hashes etc) get included without you even realizing this.
In short, to solve this problem you should define classes that exactly describe what data to send back to the client and carefully implement the logic of how to select them.
来源:https://stackoverflow.com/questions/34664715/restful-and-repository-returning-values-from-another-type-architecture