I'm on an assignment to to expose SQL data using MVC via OData.
I'm working with an existing project, Visual Studio 2015.
A bunch of tables have already been exposed.
Please first accept my apologies for perhaps a poorly crafted post.
I'm having a hard time figuring out what I'm actually working with.
In addition, I've had only a day to familiarize myself with this project.
I know that I said MVC but as far as I can tell, this project does not have VIEWS. I do believe however that the consumers of this project will read JSON.
I've used the Entity Framework to build the MODEL for the additional tables required to finish my assignment.
I'm working now on the CONTROLLER code, and I'd like to use a tool to automate that portion as much as possible. Below please find an example of a CONTROLLER already defined. I include that to help you get a feel for the type of tool I'm looking for.
Does such a tool exist? Or do I have to notepad a CONTROLLER for the tables that I've added to the project?
Thank you kindly for reading my post and for any assistance you can offer :)
public class BlockController : ODataController
{
AccordNewModel _db = new AccordNewModel();
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IHttpActionResult Get()
{
return Ok(_db.Block.AsQueryable());
}
[ODataRoute()]
[HttpPost]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IHttpActionResult Post(Block newBlock)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_db.Block.Add(newBlock);
_db.SaveChanges();
return Created(newBlock);
}
[ODataRoute()]
[HttpPut]
[EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All)]
public IHttpActionResult Put(Block block)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_db.Block.AddOrUpdate(p => new { p.BlockID }, block);
_db.SaveChanges();
return Updated(block);
}
[HttpDelete]
public IHttpActionResult Delete([FromODataUri] int key)
{
var block = _db.Block.SingleOrDefault(t => t.BlockID == key);
_db.Block.Remove(block);
_db.SaveChanges();
return Content(HttpStatusCode.NoContent, "Deleted");
}
protected override void Dispose(bool disposing)
{
_db.Dispose();
base.Dispose(disposing);
}
}
It makes little sense to write very similar controller code multiple times, I would advise against generating 1 controller per entity. Instead you could use a generic solution:
public class BaseController<T> : ODataController
{
AccordNewModel _db = new AccordNewModel();
[EnableQuery]
public IHttpActionResult Get()
{
return Ok(_db.Set<T>().AsQueryable());
}
[HttpPost]
public IHttpActionResult Post(T posted)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var added = _db.Set<T>().Add(posted);
_db.SaveChanges();
return Created(added);
}
//Etc... Write generic controller methods using Db.Set<T>()
Then for every entity you need not do much, here is how your 'block' entity controller would look:
public class BlockController : BaseController<Block> { }
For delete and update you need some way to identify the generic object of T by Id (int key). I know of two ways to do this:
1: Let your entities implement an interface IHasId which ensures they have an int Id property, then add a generic constraint to the BaseController class like so: public class BaseController<T> : ODataController where T : IHasId. Delete method could look like this:
[HttpDelete]
public IHttpActionResult Delete([FromODataUri] int key)
{
var found = _db.Set<T>().FirstOrDefault(e => e.Id == key);
if(found != null)
{
_db.Set<T>().Remove(found);
_db.SaveChanges();
return StatusCode(System.Net.HttpStatusCode.NoContent);
}
else
{
return NotFound();
}
}
Or, 2: Make the BaseController class abstract and add: protected abstract T GetById(int id);. Then inheriting classes (such as 'BlockController') must implement a method to get the object from the Db by id. You will have to implement this method for every entity, this is still less work than writing individual controllers for every entity. The delete method would look almost the same as the one above except: var found = GetById(key);.
I use Delete as an example, but if you have some way to get an entity by id you can quite easily implement the Post and possibly a Get(int key) as well.
With this generic base class the code per entity is minimal and writing it for every entity shouldn't be too much work.
Related
I have a fairly standard WebApi 2.0 OData 4.0 webservice using EF5, and code first approach. This service works and I can query entities and related entities through foreign keys.
The service is read-only and the controllers only have a Get and Get-by-key implemented.
public class MyTableController : MyDbController
{
[EnableQuery]
public IQueryable<MyTable> Get()
{
return db.MyTable;
}
[EnableQuery]
public SingleResult<MyTable> Get([FromODataUri] int key)
{
IQueryable<MyTable> result = db.MyTable.Where(p => p.pk == key);
return SingleResult.Create(result);
}
}
In both Get() implementations, I would like to have access to the list of tables that are being used in the OData and resulting SQL query. MyTable is obviously one of them, but how do I obtain the others (among others, the ones used in (nested) $expand)? I can try to parse the URL myself, but that's doesn't seem like a very good way to go about it.
Create a class CustomizeAttribute inherit from EnableQueryAttribute
Override this method : public virtual IQueryable ApplyQuery
then you get the queryOptions in this method, you can go to the SelectExpandQueryOption and find the ExpandItem, then you get all the table.
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
if (queryOptions.SelectExpand != null)
{
foreach (var selectItem in queryOptions.SelectExpand.SelectExpandClause.SelectedItems)
{
var expandedItem = selectItem as ExpandedNavigationSelectItem;
if (expandedItem != null)
{
// get the entitySetName, tableName
string entitySetName = expandedItem.NavigationSource.Name;
// can go recursive with expandItem.SelectExpandClause in case we have $epxand=A($expand=B)
}
}
}
return base.ApplyQuery(queryable, queryOptions);
}
Use this attribute on Controller method
[CustomizeAttribute]
public IQueryable<MyTable> Get()
{
return db.MyTable;
}
I've been trying to access my object <User> by using SingleResult.Create().
The issue here is that my API is returning a wrapped object containing [1] element.
{"Queryable":[{"FirstName":"John","LastName":"Doe","UserName":"JohnDoe","Id":1}]}
When using SingleResult<T>, I've seen that is possible to return 1 single element of a type like this:
{"FirstName":"John","LastName":"Doe","UserName":"JohnDoe","Id":1}
I would like to remove {"Queryable":[]} from my SingleResult<T>
Please Help :D
The problem is that your SingleResult.Create() is creating a wrapper object for your entity, where what you SEEM to want is the entity itself. Either use another method internal to your project that doesn't wrap your object (no idea what that would be), or change the signature of your method and return the entity directly.
[HttpGet]
public virtual TEntity GetById(int id)
{
try
{
var data = GetDataById(id);
return data;
}
catch (Exception e)
{
throw e;
}
}
public virtual TEntity GetbyId(int id)
{
var data = _ctx.Set<TEntity>().Where(e => e.Id == id);
var entity = data.FirstOrDefault();
return entity;
}
In our WebApi project we use EF CodeFirst approach. Also we use 2 types of databases: SQL Server and MySQL. All tables have the field ID, but in SQL Server database this field has int data type, in MySQL database this field is char(36) and contains GUID.
To solve the problem I created a custom value type like IdType and changed all model classes to use that type insted int:
public class Document
{
public IdType ID { get; set; }
public string DocumentNm { get; set; }
...
}
Then I configured the DbContext (e.g for SQL Server)
modelBuilder.Properties<IdType>().Configure(c => c.HasColumnType("int"));
...and changed repository:
public interface IRepository<T> where T : IEntity
{
IQueryable<T> GetAll();
T GetById(IdType id);
...
}
After that, when I try to go to e.g. http://localhost:7081/api/Document, it gives me an error:
Multiple actions were found that match the request: \r\nGet on type
WebUI.Controllers.API.DocumentController\r\nGetById on type
WebUI.Controllers.API.DocumentController
I use default settings of routing. Here is [HttpGet] methods from DocumentController:
public HttpResponseMessage Get() { ... }
public HttpResponseMessage GetById(IdType id) { ... }
How can I solve the problem? Could this be the cause of incorrect implementation of IdType?
P.S. I created IdType for int values as described here. if I have to add more informations, please let me know.
UPDATE
DocumentController:
public HttpResponseMessage GetById(IdType id)
{
var entity = repository.GetById(id);
if (entity == null)
{
return ErrorMsg(HttpStatusCode.NotFound, string.Format("No {0} with ID = {1}", GenericTypeName, id););
}
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
My repository:
public virtual T GetById(IdType id)
{
return GetAll().FirstOrDefault(x => x.ID == id);
}
public virtual IQueryable<T> GetAll()
{
return entities = context.Set<T>();
}
It seems that it not implemented yet in current version of Entity Framework
And as mentioned in task on GitHub
we're currently planning to work on lighting this feature up after our
initial RTM of EF7.
I have a table a with a foreign key to another table b. The EF model was generated from the database. What I want is just all the data from the table a.
But the auto-generated method
// GET: api/Massnahmentyp
public IQueryable<MASSNAHMENTYP> GetMASSNAHMENTYPs()
{
return db.MASSNAHMENTYPs;
}
returns nothing. However, the method
// GET: api/Massnahmentyp/5
[ResponseType(typeof(MASSNAHMENTYP))]
public IHttpActionResult GetMASSNAHMENTYP(decimal id)
{
MASSNAHMENTYP mASSNAHMENTYP = db.MASSNAHMENTYPs.Find(id);
if (mASSNAHMENTYP == null)
{
return NotFound();
}
return Ok(mASSNAHMENTYP);
}
works and returns the correct entry. What do I do wrong?
Update
Ok, it seems that it worked all the time, just the response was way to big because of the foreign key. Is it possible to ignore the dependencies, so that it does not join thousands of entries into the response? Table a has only 6 entries and I only want them.
What about following code?
// GET: api/Massnahmentyp
[HttpGet]
public IEnumerable<MASSNAHMENTYP> GetMASSNAHMENTYPs()
{
return db.MASSNAHMENTYPs.ToList();
}
I think you should annotate your method with [HttpGet].
Update: For the new part of your question:
You can disable lazy loading in DbContext:
public class YourContext : DbContext
{
public YourContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
I am looking for some guidance with Web API functionality that was a breeze in MVC in regards to updating entities that have navigational properties.
In MVC, it was accomplished as:
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public virtual async Task<ActionResult> Update(Page page)
{
Guard.IsNotNull(page, "page");
var pageToUpdate = await this.repository.Query.Include(p => p.Tags).Include(p => p.Name).SingleOrDefaultAsync(p => p.Pk == page.Pk);
if (pageToUpdate == null)
{
return this.RedirectToRoute(H.Constants.Routes.Error.Index, new
{
view = H.Constants.Views.Error.ViewPages.NotFound
});
}
if (this.TryUpdateModel(pageToUpdate))
{
this.repository.BeginTransaction();
this.repository.Update(pageToUpdate); // Updates related entities!
await this.repository.CommitTransactionAsync();
return this.RedirectToRoute(H.Constants.Routes.Data.Read);
}
return this.View(H.Constants.Views.FolderNames.ViewPages.FormatWith(H.Constants.Views.Data.ViewPages.Update), pageToUpdate);
}
All navigational properties would be updated, and life was well.
When attempting this exact thing in Web API, not so much. The related entities are not updated. Example:
[HttpPatch]
[HttpPut]
public virtual async Task<IHttpActionResult> Update(int pk, Page page)
{
Guard.IsNotNegativeOrZero(pk, "pk");
if (this.ModelState.IsValid)
{
if (page.Pk == pk)
{
try
{
this.repository.BeginTransaction();
this.repository.Update(page); // Doesn't update related entities.
await this.repository.CommitTransactionAsync();
return this.StatusCode(HttpStatusCode.NoContent);
}
catch (DbUpdateConcurrencyException dbUpdateConcurrencyException)
{
if (this.repository.Query.Any(p => p.Pk == pk))
{
return this.InternalServerError(dbUpdateConcurrencyException);
}
return this.NotFound();
}
}
return this.BadRequest();
}
return this.BadRequest(this.ModelState);
}
Is this possible to do today in Web API? Or is Web API currently an incomplete Microsoft product?
Edit: Updated with example and not referencing WCF
TryUpdateModel doesn't exist in WebAPI. You may also find you have issues with seralization of entities. For both those reasons I use AutoMapper to map ViewModels onto EF entities and you can use Mappings in automapper to deal with your navigation properites.
The web API method looks something like this:
public HttpResponseMessage Post(MyViewModel model)
{
if (someValidationCheckHere)
return new HttpResponseMessage(HttpStatusCode.BadRequest);
_myService.Update(Mapper.Map<MyViewModel, MyEntity>(model));
return new HttpResponseMessage(HttpStatusCode.OK);
}
As others have said the service and ultimately the repository does the update. In this example I'm ignoring the navigation property using automapper, but you can configure automapper to handle them how you like:
Mapper.CreateMap<MyViewModel, MyEntity>()
.ForMember(x => x.NavigationProperty, opt => opt.Ignore());
I set up all my mappings in a static class called on application_start in global.asax.