Given this code in a C# REST api. A request comes in and a method is invoked as below
using (MethodInformation _methodInformation = new MethodInformation())
{
_methodInformation.ObjectType = _functionElement.Type;
_methodInformation.Method = _functionElement.Method;
_methodInformation.RequestStream = ConvertRequestContent();
_methodInformation.RequestParameters = ConvertRequestParameters();
_object = _methodInformation.InstanceMethodInvoker(null);
}
All data from the request is packed up in to _methodInformation.RequestStream.
We now lose the default model binding functionality that the MVC framework provides.
Is this a legitimate way to go about this, bearing in mind we cannot use model binding and have to reconstruct the strongly typed objects from the RequestStream.
What advantages can we get from implementing the API this way?
Related
So at this point, I have two projects in my VS solution:
RecipeAPI, which is a ASP.Net Core Application
RecipeUI, which is a Windows Forms Application
Inside the RecipeAPI project, I already have the model and controller classes made and now I have to create a prototype UI in which the functionality should be implemented.
My issue is using the classes and objects from the API project, into the UI one. For example:
I am trying to populate a ListBox. Inside RecipeAPI, I have a controller class named RecipeController, which has the following method for retrieving data from the database:
[Route("v1/recipe/{recipeBy}")]
[HttpGet()]
public List<Recipe> GetRecipes(string recipeBy)
{
using (var con = _connFactory())
{
con.Open();
return con.Query<Recipe>("SELECT * FROM dbo.Recipe WHERE RecipeBy = #recipeBy", new { recipeBy }).ToList();
}
}
This was tested using PostMan, works perfectly fine. However, I am having huge issues trying to figure out how to populate the ListBox. In fact, I am having issues figuring out how I can use certain methods from a project to another. To be noted that I have already added references.
Moving forward, this is the method in the Forms class which is supposed to populate the ListBox.
private void ShowRecipes(string recipeBy)
{
List<Recipe> recipeList = new List<Recipe>;
recipeList = GetRecipes(recipeBy);
recipeListBox.DataSource = recipeList;
}
I am not entirely sure how correct this is. Basically what I am trying to do is get the list of recipes by calling the method GetRecipes() from RecipeController. Unfortunately, I am getting errors such as:
The type or namespace name 'Recipe' could not be found
The name 'GetRecipes' does not exist in the current context
So what am I missing here? Am I even on the right track or I shouldn't even try combining Web Forms and ASP.Core? And if this works too, then how can I be able to access methods from RecipeAPI.Controllers in RecipeUI Form?
You have to make Api Call to the RecipeAPI project from Windows Forms Application
in order to use the methods/logic of the APIs Project.
This is the way to make an API call:
How to make HTTP POST web request
i'm evaluating Breeze.Js for a large enterprise, data oriented, Angular 5 application in order to take advantage of the following features that are missing in the vanilla Angular framework:
client side data store
client side model state tracking
client side model validation rules
bulk data persistence (SaveChanges() method to persist all entities).
For test purposes i've written the following simple BreezeController in my ASP.NET WebApi + EntityFramework server side:
[EnableCors(origins: "*", headers: "*", methods: "*")]
[BreezeController]
public class PeopleController : ApiController
{
private AdventureWorksDbContext db = new AdventureWorksDbContext();
#region "Breeze"
readonly EFContextProvider<AdventureWorksDbContext> _contextProvider =
new EFContextProvider<AdventureWorksDbContext>();
// ~/breeze/todos/Metadata
[HttpGet]
public string Metadata()
{
return System.Text.Encoding.UTF8.GetString(AdventureWorks.WebApi.Properties.Resources.WebApiMetadata);
}
// ~/breeze/todos/Todos
// ~/breeze/todos/Todos?$filter=IsArchived eq false&$orderby=CreatedAt
[HttpGet]
public IQueryable<PersonDTO> GetPeople()
{
return db.People.ProjectTo<PersonDTO>();
}
// ~/breeze/todos/SaveChanges
[HttpPost]
public SaveResult SaveChanges(Newtonsoft.Json.Linq.JObject saveBundle)
{
return _contextProvider.SaveChanges(saveBundle);
}
#endregion
}
As you can see in my example (it uses AdventureWorks DB) i've done the following modifications:
1) "GetPeople()" endpoint returns a queryable of DTO ("ProjectTo" extension is provided by Automapper). I need to do this in order to shape the model in a usable way for the client, avoid recursions, deep dive in the schema, big fields serialization and so on.
2) "Metadata()" endpoint returns a string resource that represents metadata of the DTO class. I builded it using "PocoMetadata" tool of the "Breeze Tooling Suite" (https://github.com/Breeze/breeze.tooling). This is needed because i can't return the _contextProvider.Metadata() result as long as i'm using DTO's and not EF POCO class.
Now, if in my Angular 5 client i issue an ODATA query like the following i can see that executeQuery() method actually works:
export class BreezeDataStoreComponent implements OnInit {
private _em: EntityManager;
constructor() {
this._em = new EntityManager({
serviceName: 'http://localhost:31328/breeze/People'
});
}
ngOnInit() {
const query = EntityQuery.from('GetPeople')
.where('FirstName', FilterQueryOp.StartsWith, 'F')
.orderBy('LastName', true);
this._em.executeQuery(query).then(res => {
// Here i can get all People instances.
// Now i try to get the first, edit the name and saveChanges.
(res.results[0] as any).FirstName = 'Franklino';
this._em.saveChanges().then(saveResult => {
const test = saveResult.entities;
});
});
}
}
Unfortunately problems comes with SaveChanges().
When the Angular client calls that method, in my server side i get the following error:
System.InvalidOperationException: Sequence contains no matching
element
I think it's due to the fact that i'm calling SaveChanges() over an EF context provider passing a JObject bundle referred to DTO instead of POCO class.
So my question is:
Is it possible to use BreezeJs query and bulk persistence (SaveChanges() method) using DTO's? It's a pretty common need in big data-centric enterprise applications since i think it's a bad practice exposing EF POCOs on WebApi.
should i rely instead over a classic WebApi that respond to the POST\PUT\DELETE HTTP verbs? In that case, how to configure Breeze client in order to contact those endpoints instead of "SaveChanges" when persisting data?
If Breeze is not suitable for this needs are there other technolgies that provides the 4 abovementioned points?
Thank you very much.
To make SaveChanges work with your DTOs, you would need to either
Write your own method to unpack the JObject saveBundle, or
Use the BeforeSaveChanges method to modify the dictionary of DTOs and replace them with entities that EF understands.
Number 2 seems like the better choice. If you do not have a 1:1 match between entities and DTOs, some logic would be required when doing the mapping.
According to this tutorial: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/using-select-expand-and-value
"Web API 2 adds support for the $expand, $select, and $value options in OData. These options allow a client to control the representation that it gets back from the server"
My question it, how can I disable the representation manipulation done at the client side. In other words, my server makes sure that filtering/selecting etc. are done properly, and thus I do not want the client side to do it again. It is more of an overhead.
I think you misunderstand the purpose of query options like $expand, $select, etc. They do not cause data to be manipulated on the client. Rather, they are instructions to the service. In the Web API OData implementation, query options are typically handled by the EnableQuery attribute or the Queryable attribute. If you don't use these attributes, then you are responsible for writing the code that handles query options. Or you are free to not support them.
In your controller action, like get method, add attribute [EnableQuery] (this is for OData v4)
IN your client, send out request like ~/EntitySet?$filter=... & $select = ...
Then the response will only contain the filtered and select content.
Refer to https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataQueryableSample to see the example.
You can create a custom attribute which would inherit from EnableQueryAttribute and then override the ValidateQuery method to limit the allowed query options as well as allowed functions and page size.
using System.Net.Http;
using System.Web.OData;
using System.Web.OData.Query;
public class SecureApiQueryAttribute : EnableQueryAttribute
{
public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
base.AllowedQueryOptions = AllowedQueryOptions.None;
base.PageSize = 30;
base.AllowedFunctions = AllowedFunctions.AllFunctions;
base.ValidateQuery(request, queryOptions);
}
}
Then you can use this custom attribute like this
[SecureApiQuery]
public IHttpActionResult Get([FromODataUri] int? key = null)
{
}
Is there are a way to programmatically generate the /$metadata response returned from an ASP.Net Web Api OData controller route in a way that can be serialized to XML?
The reason I want to do this is that I'm using breeze to access the Web API using the OData adapter and would like to pre-load the Breeze MetadataStore with the metadata, like in this http://breeze.github.io/doc-js/metadata-load-from-script.htmlexample.
But this example does not seem to work with the OData adapter as it uses different metadata.
If I understand your question, you are trying to simulate GET /$metadata on the server so you can store the results in a file. In ASP.NET OData, $metadata is represented by an object that implements IEdmModel (e.g., the result of calling ODataModelBuilder.GetEdmModel). The problem then becomes how to serialize that model to XML.
The following helper will write service metadata to the given stream. For the model and config parameters, you should pass the same objects you used for your service configuration.
public class MetadataHelper
{
public static Task WriteMetadataAsync(Stream stream, IEdmModel model, HttpConfiguration config, string odataRouteName)
{
var request = new HttpRequestMessage(HttpMethod.Get, "/$metadata");
request.ODataProperties().Model = model;
request.ODataProperties().RouteName = odataRouteName;
request.SetConfiguration(config);
var payloadKinds = new List<ODataPayloadKind> { ODataPayloadKind.MetadataDocument };
var xmlMediaType = new MediaTypeHeaderValue("application/xml");
var formatter = new ODataMediaTypeFormatter(payloadKinds).GetPerRequestFormatterInstance(model.GetType(), request, xmlMediaType);
var content = new StringContent(String.Empty);
content.Headers.ContentType = xmlMediaType;
return formatter.WriteToStreamAsync(model.GetType(), model, stream, content, null);
}
}
True, the OData metadata is handled correctly by Breeze only when reading an OData response; the MetadataStore doesn't import/export it directly.
I think the easiest way to handle this is to create a separate bit of client-side code that will
Create an EntityManager that will
Fetch the metadata from the OData server
Export the metadata from the MetadataStore
Log the metadata so you can capture it and store it in a file
Not elegant, but it gets the job done.
Some future version of breeze.server.net will do the OData-to-Breeze metadata conversion on the server, so we won't have this problem.
We are developing large MVC project and we have an intension to use HttpSessionStateWrapper (as well as HttpRequestWrapper, HttpResponseWrapper, etc) to add extended functionalities do this objects. It would be adding session messages, additional collections, html metadata with response - stuff like that, managable from controllers and accessible in the views when needed.
I have done it in a smaller project and it gennerally worked well, except some casting issues here and there, but it can be worked around by not using wrappers outside controllers or eventually views. Every controller would be a custom controller with a code like that:
public class Controller : System.Web.Mvc.Controller
{
public new CustomHttpResponse Response
{
get
{
return (CustomHttpResponse)HttpContext.Response;
}
}
public new CustomHttpRequestRequest
{
get
{
return (CustomHttpRequestRequest)HttpContext.Request;
}
}
//etc...
}
ContextWrapper would be created in a custom MvcHandler. Response, request and session wrappers would be created and taken from ContextWrapper .
Is this a good policy to use wrappers to extend functionalities, or they where intended only for creating testing mocks?