I want to get additional data that is in the body of a request
my code
public class TicketController : ReadWriteController<TicketView, Ticket>
{
public override ReadWriteBusiness<TicketView, Ticket> Business => new TicketBusiness();
protected override ReadBusiness<TicketView> ReadBusiness => new TicketBusiness();
public override IActionResult Create([FromBody] Ticket model)
{
var message = HttpContext.GetBody().Result;
return OkJson("ok");
}
}
and
using Microsoft.AspNetCore.Http;
using System.IO;
using System.Threading.Tasks;
namespace PaydarCore.Api;
public static class ExtensionMethods
{
public static async Task<string> GetBody(this HttpContext httpContext)
{
httpContext.Request.EnableBuffering();
httpContext.Request.Body.Position = 0;
var rawRequestBody = await new StreamReader(httpContext.Request.Body).ReadToEndAsync();
return rawRequestBody;
}
}
my model
public class Ticket : Entity
{
public Ticket()
{
RelatedItems = new ExpandoObject();
}
public override long Id { get; set; }
public int TicketNumber { get; set; }
public string Subject { get; set; }
public long? TicketDepartmentId { get; set; }
public long TicketPriorityId { get; set; }
public long TicketStateId { get; set; }
}
my body request
{
"ticketNumber" : 12,
"subject" :"",
"ticketPriorityId":1,
"ticketStateId": 1,
"message":"fjhsdkfhjsdfhkjsdhf"
}
My goal is to get the value "message" in request body
That this property does not exist in my model
But there is no value in the body
I found by testing that removing the FromBody puts the value in the body
How can I get the body value?
----edit---
I noticed that by adding this code to Startup the problem is solved. Is there another way?
app.Use(next => context =>
{
context.Request.EnableBuffering();
return next(context);
});
Related
I have a small database that has been created by EF using a typical model class:
public class Metrics
{
public int Id { get; set; }
public string? MetricValue { get; set; }
public string? MetricHost { get; set; }
public string? MetricTime { get; set; }
}
The database is now populated with data and my Minimal API can return all the entries from:
app.MapGet("/metric", async (DataContext context) => await context.Metrics.ToListAsync());
And also, I can query by Id:
app.MapGet("/metric/{id}", async (DataContext context, int id) =>
await context.Metrics.FindAsync(id) is Metric metric ?
Results.Ok(metric) :
Results.NotFound("Metric not found"));
I've been searching the web for something that would show how to search by another property but have not found anything that works. e.g.,
app.MapGet("/hostnames/{MetricHost}"...
This article on CodeMaze is the closest I've found but none of the examples seem to work:
https://code-maze.com/aspnetcore-query-string-parameters-minimal-apis/
Any help is appreciated. Here's an example that did not work:
app.MapGet("/search", (SearchCriteria criteria) =>
{
return $"Host: {criteria.MetricHost}, Id: {criteria.Id}";
});
With model changes:
public class Metric
{
public int Id { get; set; }
public string? MetricValue { get; set; }
public string? MetricHost { get; set; }
public string? MetricTime { get; set; }
public static ValueTask<Metric?> BindAsync(HttpContext context, ParameterInfo parameter)
{
string hostname = context.Request.Query["MetricHost"];
int.TryParse(context.Request.Query["Id"], out var id);
var result = new Metric
{
MetricHost = hostname,
Id = id
};
return ValueTask.FromResult<Metric?>(result);
}
}
You are binding wrong type, BindAsync should be part of SearchCriteria:
app.MapGet("/search", (SearchCriteria criteria, DataContext context) =>
{
IQueryable<Metric> query = context.Metrics;
if(criteria.MetricHost is not null)
{
query = query.Where(m => m.MetricHost == criteria.MetricHost)
}
// ... rest of filters
return await query.ToListAsync();
});
public class SearchCriteria
{
public string? MetricHost { get; set; }
// ... rest of filters
public static ValueTask<SearchCriteria?> BindAsync(HttpContext context, ParameterInfo parameter)
{
string hostname = context.Request.Query["MetricHost"];
// ... rest of filters
var result = new SearchCriteria
{
MetricHost = hostname,
};
return ValueTask.FromResult<SearchCriteria?>(result);
}
}
Read more:
Filtering in EF Core
I am practicing with web api. My goal is to create a Get endpoint, which receive data from an external api, then return a different result. external api link: https://www.themealdb.com/api/json/v1/1/search.php?f=a, The external api data looks like:
{
"meals": [
{
"idMeal": "52768",
"strMeal": "Apple Frangipan Tart",
"strDrinkAlternate": null,
"strCategory": "Dessert",
.....
},
{
"idMeal": "52893",
"strMeal": "Apple & Blackberry Crumble",
....
}
]
}
I want my endpoint provide a different result like the following:
[
{
"idMeal": "52768",
"strMeal": "Apple Frangipan Tart",
"ingredients": ["Apple", "sugar"...]
},
{
"idMeal": "52893",
"strMeal": "Apple & Blackberry Crumble",
"ingredients": ["Apple", "sugar"...]
}
]
The following code is what I attempted so far, It's working, but the moment I changed property ingredient1 from public to private, that ingredient in list will become null, also, there are so many ingredients, some of them are null by default, I don't want to add them if they are null, how can I fix these two issues? Thanks a lot
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Mvc;
using RestSharp;
namespace testAPI.Controllers;
public class Content
{
[JsonPropertyName("meals")]
public List<Meal> Meals { get; set; }
}
public class Meal
{
[JsonPropertyName("idMeal")]
public string MealId { get; set; }
[JsonPropertyName("strMeal")]
public string Name { get; set; }
[JsonPropertyName("strIngredient1")]
public string Ingredient1 { get; set; }
[JsonPropertyName("strIngredient2")]
public string Ingredient2 { get; set; }
[JsonPropertyName("strIngredient20")]
public string Ingredient20 { get; set; }
public List<string> Ingredients
{
get { return new List<string>(){Ingredient1, Ingredient2, Ingredient20};}
}
}
[ApiController]
[Route("api/[controller]")]
public class DishesController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetAllRecipes()
{
var client = new RestClient($"https://www.themealdb.com/api/json/v1/1/search.php?s=");
var request = new RestRequest();
var response = await client.ExecuteAsync(request);
var mealList = JsonSerializer.Deserialize<Content>(response.Content);
return Ok(mealList.Meals);
}
}
To address the problems one at a time...
the moment I changed property ingredient1 from public to private, that ingredient in list will become null
Changing the access modifier affects both deserialization and serialization, so this cannot be used to only stop it from serializing the property. You should split the data models up into what you want to receive and what you want to expose/return.
there are so many ingredients, some of them are null by default, I don't want to add them if they are null
Addition to splitting up the data models you can handle this when mapping from one model to the other.
The following code should fix both issues:
namespace TheMealDb.Models
{
// These are the models you receive from TheMealDb
// JSON converted to classes with https://json2csharp.com/
public class Root
{
public List<Meal> meals { get; set; }
}
public class Meal
{
public string idMeal { get; set; }
public string strMeal { get; set; }
public string strIngredient1 { get; set; }
public string strIngredient2 { get; set; }
public string strIngredient3 { get; set; }
// Other properties removed for brevity...
}
}
namespace Internal.Models
{
// This is the model you want to return from your controller action
public class Meal
{
[JsonPropertyName("id")] // No need to use the same name as from themealdb
public string Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("ingredients")]
public List<string> Ingredients { get; set; }
}
}
Now, to fetch, map and return the data in your controller action:
[HttpGet]
public async Task<IActionResult> GetAllRecipes()
{
var client = new RestClient($"https://www.themealdb.com/api/json/v1/1/search.php?s=");
var request = new RestRequest();
var response = await client.ExecuteAsync(request);
// Deserialize to the "TheMealDb" models
var mealList = JsonSerializer.Deserialize<TheMealDb.Models.Root>(response.Content);
// Map to your own models
var myMealList = mealDbList.meals?.Select(MapToInternal);
return Ok(myMealList);
}
// Map "TheMealDb" model to your own model
private Internal.Models.Meal MapToInternal(TheMealDb.Models.Meal externalMeal)
{
return new Internal.Models.Meal
{
Id = externalMeal.idMeal,
Name = externalMeal.strMeal,
Ingredients = new []
{
externalMeal.strIngredient1,
externalMeal.strIngredient2,
externalMeal.strIngredient3,
// ...
}
// Remove empty/null ingredients
.Where(ingr => !string.IsNullOrEmpty(ingr))
.ToList()
};
}
See the code in action.
Im currently trying to implement CRUD functionality with a dbfactory and generics with microsoft EF, but while listing entries is working, making changes to the db is currently not working.
public class AbstractDataModel
{
[Key]
public Guid gid { get; set; }
}
Model
class SalesOrder : AbstractDataModel
{
public int salesOrderID { get; set; }
public int productID { get; set; }
public int customerID { get; set; }
public Guid createdBy { get; set; }
public string dateCreated { get; set; }
public string orderDate { get; set; }
public string orderStatus { get; set; }
public string dateModified { get; set; }
}
A DBCore with some other functionality besides the ones listed here, which are not relevant for the factory
public class DBCore : DbContext
{
public static string connectionString = "myConnectionStringToDb";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
Data Service which calls factory
class SalesOrderService : DBCore
{
public DbSet<SalesOrder> SalesOrders { get; set; }
public OkObjectResult GetAllSalesOrders()
{
DBFactory factory = new DBFactory();
return new OkObjectResult(JsonConvert.SerializeObject(factory.GetAll(SalesOrders)));
}
public OkObjectResult AddSalesOrder(SalesOrder order)
{
order.gid = Guid.NewGuid();
return DBFactory.AddOne(order);
}
public OkObjectResult UpdateSalesOrder(SalesOrder order)
{
return DBFactory.UpdateOne(order);
}
public OkObjectResult DeleteSalesOrder(SalesOrder order)
{
return DBFactory.DeleteOne(order);
}
}
simple CRUD-Factory,
class DBFactory : DBCore
{
public DbSet<UserModel> Users { get; set; }
public DbSet<SalesOrder> SalesOrders { get; set; }
public List<T> GetAll<T>(DbSet<T> dbset) where T : class
{
using (this)
{
return dbset.ToList();
}
}
public static OkObjectResult AddOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Add(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully added");
}
}
public static OkObjectResult UpdateOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Update(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully updated");
}
}
public static OkObjectResult DeleteOne<T>(T data)
{
using (DBFactory factory = new DBFactory())
{
factory.Attach(data);
factory.Remove(data);
factory.SaveChanges();
return new OkObjectResult("Entry was sucessfully removed");
}
}
}
Edit: Following the advices i changed the code so it should SaveChanges for the Factory, which also contains the context as a property. But it still doesnt seem to work for all database operations except listing all entries
Editv2: Thanks for the adivces it seems i have solved that problem, but a new one appeared :D
I can now do database operations like deleting entries, but now i cant list the entries anymore because the following error occurs, although the code there didnt really change:
"Executed 'GetAllOrders' (Failed, Id=5fb95793-572a-4545-ac15-76dffaa7a0cf, Duration=74ms)
[2020-10-23T14:33:43.711] System.Private.CoreLib: Exception while executing function: GetAllOrders. Newtonsoft.Json: Self referencing loop detected for property 'Context' with type 'FicoTestApp.Models.SalesOrder'. Path '[0].ChangeTracker'."
try adding
services.AddControllers().AddNewtonsoftJson(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
to your
startup.cs
it should to the job
Currently I am doing an API call via jQuery, my question is, is there away to do this call in C# or away to convert the results of an API call to an ASP.NET List of Model Objects?
Here is my Model
public class TeamStatsClass
{
public int id { get; set; }
public string name { get; set; }
public string league { get; set; }
public string division { get; set; }
}
And here is my current ajax call
$.ajax({
url: "https://statsapi.web.nhl.com/api/v1/teams?sportId=1",
success: function (data) {
for (var team of data.teams) {
console.log(team.name);
}
}
});
UPDATE
I changed my classes to look like so:
public class StatsTeamsClass
{
public IEnumerable<Teams> teams { get; set; }
public string copyright { get; set; }
}
public class Division
{
public int id { get; set; }
public string name { get; set; }
public string link { get; set; }
}
public class Teams
{
public int id { get; set; }
public string name { get; set; }
public string link { get; set; }
public League league { get; set; }
public Division division { get; set; }
}
and created this method which indeeds puts the results in model object:
public async System.Threading.Tasks.Task<StatsTeamsClass> GetTeams()
{
HttpClient Http = new HttpClient();
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
StatsTeamsClass teams = JsonConvert.DeserializeObject<StatsTeamsClass>(json);
return teams;
}
But when I try to call this method in another controller, it just hangs there, no error, no nothing, I am assuming it will just time out after a while
public class HomeController : Controller
{
APIController webService = new APIController();
public ActionResult Index()
{
var item = webService.GetTeams().Result.teams;
return View();
}
}
(GetTeams() is inside the controller APIController)
So what would be the proper way to A. get the results of an API in object model and then call those results?
The controller action needs to be made async as well to avoid mixing async-await and blocking calls like .Result or .Wait() that could potentially cause deadlocks.
Reference Async/Await - Best Practices in Asynchronous Programming
public class HomeController : Controller {
APIController webService = new APIController();
public async Task<ActionResult> Index() {
var model = await webService.GetTeams();
var teams = model.teams;
return View();
}
}
Assuming APIController is an actual ApiContoller
public class APIController : ApiController {
//Your original code
public async Task<StatsTeamsClass> GetTeams() {
HttpClient Http = new HttpClient();
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
StatsTeamsClass teams = JsonConvert.DeserializeObject<StatsTeamsClass>(json);
return teams;
}
//...
}
I would suggest not calling APIController directly like that from the HomeController and instead extract the GetTeams() method out into a reusable service
public class WebService {
static Lazy<HttpClient> http = new Lazy<HttpClient>();
public async Task<T> GetAsync<T>(string url) {
var json = await http.Value.GetStringAsync(url);
return JsonConvert.DeserializeObject<T>(json);
}
public Task<StatsTeamsClass> GetTeamsAsync() {
var url = "https://statsapi.web.nhl.com/api/v1/teams?sportId=1";
return GetAsync<StatsTeamsClass>(url);
}
}
Reference You're using HttpClient wrong
that can be properly used in HomeController
public class HomeController : Controller {
public async Task<ActionResult> Index() {
// Ideally web service should be injected but that topic
// is outside of the scope of the question at the moment.
var webService = new WebService();
var model = await webService.GetTeamsAsync();
var teams = model.teams;
//...
return View(teams);
}
}
The assumption here is that the project is a mixed Asp.Net MVC and Web Api 2+
Index.cshtml
#model IEnumerable<Teams>
#{
ViewBag.Title = "Teams";
}
#if(Model != null && Model.Count() > 0) {
#foreach (var #team in Model) {
<p>#team.name</p>
}
}
Yes, the equivalent in C# would be to use HttpClient. You're best off creating a static instance of the class that you reuse for a particular kind of repeated call:
private static readonly HttpClient Http = new HttpClient();
and then used it from an async method using Newtonsoft.Json like this:
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
You can then parse this string of JSON into a model class like this:
var model = JsonConvert.DeserializeObject<TeamStatsClass>(json);
As the question is answered by #Daniel above just want to add couple of more points here The json you are getting cannot be directly casted to TeamStatsClass you might have to introduce another base class as teams is the collection in the json you are getting.
Im posting it here to get a clearer view
public class ResponseBaseClass
{
public IEnumerable<TeamStatsClass> teams { get; set; }
public string copyright { get; set; }
}
public class TeamStatsClass
{
public int id { get; set; }
public string name { get; set; }
public Division division { get; set; }
}
public class Division
{
public int id { get; set; }
public string name { get; set; }
public string nameShort { get; set; }
public string link { get; set; }
}
HttpClient Http = new HttpClient();
var json = await Http.GetStringAsync("https://statsapi.web.nhl.com/api/v1/teams?sportId=1");
var model = JsonConvert.DeserializeObject<ResponseBaseClass>(json);
var yourTeamModelObj = model.teams;
I am trying to use ServiceStack's AutoQuery with a service source, but am either unable to get caching working correctly, or have misunderstood how it is supposed to work.
What I am trying to achieve to to add query functionality to an 'edge' microservice which calls an internal service that serves up a complete list of data.
Minimal code to reproduce my problem:
class Program
{
static async Task Main(string[] args)
{
IWebHost host = new WebHostBuilder()
.UseKestrel((builderContext, options) => options.Configure(builderContext.Configuration.GetSection("Kestrel")))
.UseStartup<Startup>()
.Build();
await host.RunAsync();
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services) {}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseServiceStack(new AppHost());
app.Run(context => Task.FromResult(0));
}
}
public class AppHost : AppHostBase
{
public AppHost() : base("Hello Web Services", typeof(HelloService).Assembly){ }
public override void Configure(Funq.Container container)
{
container.AddSingleton<ICacheClient, MemoryCacheClient>(); // Otherwise HostContext.Cache is null
Plugins.Add(new AutoQueryDataFeature { MaxLimit = 3, IncludeTotal = true }.AddDataSource(ctx => ctx.ServiceSource<string>(new Hello(), HostContext.Cache, TimeSpan.FromMinutes(5))));
}
}
// Request DTO
[Route("/hello")]
[Route("/hello/{Name}")]
public class Hello : QueryData<NameDto>
{
[QueryDataField(Condition = "StartsWith", Field = nameof(Name))]
public string Name { get; set; }
}
public class NameDto
{
public string Name { get; set; }
}
public class HelloService : Service
{
public IAutoQueryData AutoQuery { get; set; }
public async Task<object> Any(Hello query)
{
//Imagine I was making a service call to another microservice here...
var data = new List<NameDto> { new NameDto { Name = "Bob" }, new NameDto { Name = "George" }, new NameDto { Name = "Baldrick" }, new NameDto { Name = "Nursey" }, new NameDto { Name = "Melchett" }, new NameDto { Name = "Kate" } };
DataQuery<NameDto> dataQuery = AutoQuery.CreateQuery(query, Request, new MemoryDataSource<NameDto>(data, query, Request));
return AutoQuery.Execute(query, dataQuery);
}
}
Nuget packages: Mircosoft.AspNetCore.All (2.2.1) and ServiceStack (5.4.0)
So, in a console (.NET Core 2.2), the above code will spin up and listen on port 5000.
If I query, I get my list, which is limited to the number of results as expected, and I can also skip / take as expected.
However, every time I invoke the service method, the results are not cached (which is specified when I registered the plug-in - cache for 5 minutes) and if I put a breakpoint in the service method, the list of 'Names' is re-created every time. This happens even if I make the same request to the service.
I'd like to be able to cache the result set (in memory is fine) and only hit the service method when the cache expires. What am I doing wrong (or misunderstanding) here?
Edit
Code that I used to try out Mythz suggestion... now I don't get any autoquery features working at all.
class Program
{
static async Task Main(string[] args)
{
IWebHost host = new WebHostBuilder()
.UseKestrel((builderContext, options) => options.Configure(builderContext.Configuration.GetSection("Kestrel")))
.UseStartup<Startup>()
.Build();
await host.RunAsync();
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseServiceStack(new AppHost());
app.Run(context => Task.FromResult(0));
}
}
public class AppHost : AppHostBase
{
public AppHost() : base("Hello Web Services", typeof(HelloService).Assembly){ }
public override void Configure(Funq.Container container)
{
container.AddSingleton<ICacheClient, MemoryCacheClient>();
Plugins.Add(new AutoQueryDataFeature { MaxLimit = 5 }
.AddDataSource(ctx => ctx.ServiceSource<GithubRepo>(ctx.Dto.ConvertTo<QueryGithubRepo>(),
HostContext.Cache, TimeSpan.FromMinutes(5))));
}
}
public class QueryGithubRepo : QueryData<GithubRepo>
{
public string User { get; set; }
public string Organization { get; set; }
}
public class GithubRepo
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Homepage { get; set; }
public int Watchers_Count { get; set; }
public int Stargazers_Count { get; set; }
public int Size { get; set; }
public string Full_Name { get; set; }
public DateTime Created_at { get; set; }
public DateTime? Updated_At { get; set; }
public bool Has_Downloads { get; set; }
public bool Fork { get; set; }
public string Url { get; set; } // https://api.github.com/repos/NetCoreWebApps/bare
public string Html_Url { get; set; }
public bool Private { get; set; }
public GithubRepo Parent { get; set; } // only on single result, e.g: /repos/NetCoreWebApps/bare
}
public class NameDto
{
public string Name { get; set; }
}
public class HelloService : Service
{
public object Get(QueryGithubRepo request)
{
if (request.User == null && request.Organization == null)
throw new ArgumentNullException("User");
var url = request.User != null
? $"https://api.github.com/users/{request.User}/repos"
: $"https://api.github.com/orgs/{request.Organization}/repos";
return url.GetJsonFromUrl(requestFilter: req => req.UserAgent = GetType().Name)
.FromJson<List<GithubRepo>>();
}
}
If you're using AutoQuery in your Service implementation that's just a Custom AutoQuery implementation not a AutoQuery Service Data Source which queries the results of a normal Service.
In this case it sounds like you do want a cacheable Auto Query Service Data Source which the docs shows an example of in its GetGithubRepos Service which makes an API call to GitHub's 3rd Party API:
public class QueryGithubRepo : QueryData<GithubRepo>
{
public string User { get; set; }
public string Organization { get; set; }
}
public object Get(GetGithubRepos request)
{
if (request.User == null && request.Organization == null)
throw new ArgumentNullException("User");
var url = request.User != null
? $"https://api.github.com/users/{request.User}/repos"
: $"https://api.github.com/orgs/{request.Organization}/repos";
return url.GetJsonFromUrl(requestFilter:req => req.UserAgent = GetType().Name)
.FromJson<List<GithubRepo>>();
}
Then you register it is a cached Service Data Source when registering the Service DataSource:
Plugins.Add(new AutoQueryDataFeature { MaxLimit = 100 }
.AddDataSource(ctx => ctx.ServiceSource<GithubRepo>(ctx.Dto.ConvertTo<GetGithubRepos>(),
HostContext.Cache, TimeSpan.FromMinutes(5)));
);
You can use HostContext.LocalCache to cache it in the local Memory Cache, instead of the registered ICacheClient caching provider.