I'm trying to learn Blazor while bringing some of my ideas to live as I think that the best way to learn is by practice. I want to build a card viewer for CCG (collectible card game) and I've stumbled upon a roadblock which I hope someone can help me with :)
As I don't want to store the card database myself, I'm using a 3rd party source to download the required information - and I have a problem with it's storage afterwards.
I made a server-side controller that fetches the mentioned DB (after some sorting with my own class to filter some unnecesarry data). What I wanted to achieve is a list of all the cards and onClick to fetch the info about the specific one by ID.
My controller looks like this:
[Route("[controller]")]
[ApiController]
public class CardController : ControllerBase
{
private List<Card> Cards = new();
[HttpGet("{id}")]
public ActionResult Get(int id)
{
var card = Cards.Find(o => o.Id == id);
if (card != null)
{
return Ok(card);
}
return NotFound("Card not found");
}
[HttpGet("List")]
public ActionResult List()
{
return Ok(Cards);
}
[HttpGet("GetAllCards")]
public async Task<List<Card>> GetAllCards()
{
if (Cards.Count() == 0)
{
Cards = await GetAllCardsPro();
}
return Cards;
}
public async Task<List<Card>> GetAllCardsPro()
{
var client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("https://db.ygoprodeck.com/api/v7/cardinfo.php");
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
YgoProMap.Rootobject obj = JsonConvert.DeserializeObject<YgoProMap.Rootobject>(responseBody);
if (obj != null)
{
foreach (var card in obj.data)
{
Cards.Add(new Card
{
Id = card.id,
Name = card.name,
Type = card.type,
Desc = card.desc,
Atk = card.atk,
Def = card.def,
Level = card.level,
Race = card.race,
Attribute = card.attribute,
Archetype = card.archetype,
Linkval = card.linkval,
Scale = card.scale,
Images = new string[] { card.card_images.First().image_url, card.card_images.First().image_url_small }
});
}
}
return Cards;
}
}
The problem occurs when I try to call Get(ID) to get info of that specific card and it always return 404. If I manualy populate the Cards list with a new Card(some data) I'm able to get that one specific card's information. So I would assume that after the call to that 3rd party API Cards it just passes the value to client once remaining null on the server.
So my 1st question here is how should I store that data that is fetched from the 3rd party API to make it accessable afterwards? Should it be somewhere in the server startup with a daily refresh? Is it even possible to store it like a JSON file for example?
My 2nd question is about best practices performance wise as at the moment I'm passing the whole List of Cards to the client (~10 thousand objects). Would it be better to pass a separate array that only stores card.Name and card.Id for it to be bound to a UI list/table and afterwards onClick fetch that specific card's full data? Or 10 thousand objects isn't a huge deal and I should just pass the whole List and do all the specific card operation client side?
Sorry for making it so long but I've tried to be as specific as I can :D
Related
I am at a loss for where to look to understand how to use EF Core and ASP.NET Core Web API.
I am building a small marina manager and I am able to get my independent list of marinas and my independent list of docks but I want to get a list of all docks that belong to a Marina
These are my database tables:
Marina
Int Id
String Name
Dock
Int Id
Int MarinaId
String Name
I am attempting to output all docks for a given MarinaID where the MarinaId is equal to the input passed. But I am running into issues everywhere I turn.
I am not asking for the solution here just advice or direction on where I can figure this out. I have tried using _context.Dock.Find which wasn't successful. Please point me in the direction of the right documentation to help me solve this issue.
I am currently using ControllerBase.
Below is how I get the results for all of the docks or marinas respectively I just change the name of the field from Marina to Docks for docks call but its essentially the same. I have tried passing an int and even the model but ultimately I need to find the syntax to properly do what I am attempting.
[Route("List")]
[AllowAnonymous]
[HttpGet]
public async Task<ActionResult<List<Marina>>> GetResultAsync()
{
var itemLst = _context.Marina.ToList();
var results = new List<Marina>(itemLst);
await Task.CompletedTask;
return results;
}
I have tried this also to no avail it just lists all of the docks still
public async Task<ActionResult<List<Dock>>> ListDocks(int marinaId)
{
var itemList = _context.Dock.Select(dock => new Dock
{
Id = dock.Id,
Name = dock.Name,
MarinaId = marinaId
}).ToList();
var results = new List<Dock>(itemLst);
await Task.CompletedTask;
return results;
}
public async Task<ActionResult<List<Dock>>> ListDocks(int marinaId)
{
return _context.Dock.Where(d => d.MarinaId == marinaId).ToList();
}
I am writing a chat bot that can ask a user for name and a requirement to search job with.
Name and Requirement will be stored in UserData and PrivateConversationData.
I am trying to send the requirement as an index, 1-5, to a method dialog and to specify a requirement like salary amount and then call an appropriate api. But the bot keep giving an error when passing the parameter. How can I fix the error? Is it the way my method receives it or I use the wrong way to call it?
I'm trying to combine the requirement and job stuff into one single to prevent [Community Edit: To prevent what?]
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
if (string.IsNullOrEmpty(name))//ask for the name
{
//code for get name
}
else
{
context.PrivateConversationData.TryGetValue<int>("Index", out int index);
if (!Enumerable.Range(1, 5).Contains(index))
{
var getRequirement =
FormDialog.FromForm(Requirement.BuildForm,
FormOptions.PromptInStart);
context.Call(getRequirement, AfterGetRequirementAsync);//able to get requirement index as int 1~5. next going to ask what specific value
}
else
{
await context.PostAsync($"{name}:{index}: {activity.Text}");//testing: it is able to print the name and the index user choose
context.Wait(MessageReceivedAsync);
}
}
I am using context.Call(getRequirement, AfterGetRequirementAsync) to try to get both requirement and then ask for the specific value in AfterGetRequirementAsync.
private async Task AfterGetRequirementAsync(IDialogContext context, IAwaitable<Requirement> result)
{
//Requirement requirementindex = null;
var requirementindex = await result;
context.PrivateConversationData.SetValue<int>("Index", requirementindex.Index);
await context.PostAsync($"Result:{JsonConvert.SerializeObject(requirementindex)}");//for testing index after user's input about index
var jobs = await GetJobDialog.LoadJob(requirementindex.Index);//Im trying to pass the .Index
//await context.PostAsync($"Job Search Result : {Environment.NewLine}{JsonConvert.SerializeObject(jobs)}");//trying to print the list of result to user
context.Wait(MessageReceivedAsync);
}
In AfterGetRequirementAsync, it is able to get the requirementindex and I can store it in PrivateConversationData in MessageReceivedAsync as Index. But when I try to pass the requirementindex.Index to GetJobDialog.JobModel.LoadJob it give me error of [Community Edit: What's the error?]
public class GetJobDialog
{
public Task StartAsync(IDialogContext context)
{
context.Wait(LoadJob(context.UserData));
return Task.CompletedTask;
}
public static async Task<List<JobModel>> LoadJob(IDialog context, IAwaitable<JobResultModel> result, int index)//depends on the job searching index, using different method to search.
{//it should return list of jobs to user.
string url = "";
if (index == 1)//posting type
{ //code like index == 4 }
else if (index == 2)//level
{ //code like index == 4 }
else if (index == 3)//full time or part time
{ //code like index == 4 }
else if (index == 4)//salary from
{ //ask for internal or external and change the end= to match value
url = $"https://data.cityofnewyork.us/resource/kpav-sd4t.json?salary_range_from=40000";
}//the only thing different is after .json? the index = specific value
else if (index == 5)//salary to
{ //code like index == 4 }
else//if not getting any valid option, throw error message and ask for index again
{
}
using (HttpResponseMessage response = await ApiHelper.ApiHelper.ApiClient.GetAsync(url))
{
if (response.IsSuccessStatusCode)
{
JobResultModel job = await response.Content.ReadAsAsync<JobResultModel>();
return job.Results;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
I am also trying to get the user to input the specific amount in the GetJobDialog. That way, the user doesn't have to enter anything to trigger the chat bot again.
I'm just posting the API caller incase I have some mistake because I learn all these by myself and do not have a clear understanding of how C# and api work.
public static class ApiHelper
{
public static HttpClient ApiClient { get; set; }
public static void InitializeClient()
{
ApiClient = new HttpClient();
ApiClient.DefaultRequestHeaders.Accept.Clear();
ApiClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
}
I expect the chat bot be able to pass the index to LoadJob and ask for specific value before or after the call of LoadJob. Then response a list of job with different field to the user.
I see a few issues with your code that might be causing this. If you can link me to all of your code, I might be able to help debug. In the meantime, here's some things might be causing the problem:
You're using BotBuilder V3 Code. If you're writing a new bot, you should really be using Botframework V4. Here's a State Management Sample Bot that can help you get started. You really should switch to V4, especially if this is a newer bot. If you run into issues in the future, you'll receive better support.
It looks like you're saving Index to PrivateConversationData, but when you pass it LoadJob(), you're using UserData instead.
I'm not sure that you can pass information when using context.Wait(). None of the V3 Samples do that. I don't use V3 much, so I can't tell for sure. What you should instead do, is use something like context.PrivateConversationData.TryGetValue<int>("Index", out int index); to load the index instead of passing it.
It also looks like you didn't post the error message. If you can post that and all of your code, I can help debug further (if the above doesn't work).
I am currently working on an API where a record should only be allowed to be pulled once. It's basically a queue where once a client pulls the record, the Retrieved field on the record is marked true. The Get calls only pull records where the Retrieved field is false.
Controller:
[HttpGet]
public virtual IActionResult GetAll([FromQuery] int? limit)
{
try
{
return Ok(_repository.Get(limit));
}
catch
{
return new StatusCodeResult(StatusCodes.Status500InternalServerError);
}
}
Repository:
public IQueryable<Report> Get(int? limit)
{
IQueryable<Report> reports;
if (limit == null)
{
reports = _context.Reports.Where(r => r.Retrieved == false);
}
else
{
reports = _context.Reports.Where(r => r.Retrieved == false).Take((int)limit);
}
return reports;
}
What would be the best way to modify the records that have been pulled by the Get call? If I do the modification before returning results from the repository code, then when the controller actually converts the IQueryable to real data, the field has changed and it won't pull any results, but the Controller seems like the wrong place to be doing this sort of modification to the database.
I would split this functionality away from the retrieval. Let the caller/client indicate that the report has been successfully retrieved and read with a second call. It is a little more overhead but it adds resilience. Example: if there is a failure in the retrieval after the server call (maybe in the network on browser or client app) then the client has another opportunity to retrieve the data.
Controller:
[HttpPut]
public virtual async Task<IActionResult> MarkAsRetrieved(IEnumerable<int> reportIds, CancellationToken token)
{
await _repository.MarkRetrievedAsync(reportIds, token).ConfigureAwait(true);
return Ok();
}
Repository:
public Task MarkRetrievedAsync([FromBody]IEnumerable<int> reportIds, CancellationToken token)
{
foreach (Report report in reportIds.Select(x => new Report{ReportId = x, Retrieved = false}))
{
_context.Reports.Attach(report);
report.Retrieved = true;
}
return _context.SaveChangesAsync(token);
}
Notes
It is only necessary to send over the identifier for a Report instance. You can then attach an empty instance with that same identifier and update the Retrieved property to true, just that will be sent in the corresponding store update statement.
I would changed the Retrieved bit in the database to a handle of some kind-- Guid or record id to another table that records the fetch, or some other unique value. Then I would determine the handle, update the records I am about to fetch with that that handle, then retrieve the records that match that handle. At any point if you fail, you can set the retrieved handle back to NULL for the handle value you started.
I'm trying to make a request every 10 seconds to a webApi controller in ASP.NET. The controller has to check in the database (SQL Server) if there are new data available (Using enitity framework). Here is the controller side
public class RemoteCommandController : ApiController
{
public Command Get(String id)
{
Command command = null;
ProxyCommand proxycommand = null;
Device deviceSearch;
using (var systemDB = new DB())
{
deviceSearch = systemDB.Devices.Include("CommandList").Where(d => d.Name.Equals(id)).SingleOrDefault();
command = deviceSearch.CommandList.LastOrDefault();
}
if (command.IsExecuted == false)
{
proxycommand = ConvertingObjects.FromCommandToProxy(command);
}
return proxycommand;
}
}
I want to avoid to query the DB everytime I call this controller. Is there a way to improve this controller in order to reduce the number of querys?
I was thinking of using Session, but I don't think is a good idea...
Just use output caching. Add this attribute to your controller:
[OutputCache(Duration=10, VaryByParam="none")]
public class RemoteCommandController : ApiController
{
//etc.....
Increase or decrease the duration as needed. If the command list never changes, you can set the duration absurdly high and the controller will only get called once.
I have a method that is looping through a list of values, what I would like to do is when I open the page to be able to see the values changing without refreshing the current view. I've tried something like the code bellow.
public static int myValueReader { get; set; }
public static void ValueGenerator()
{
foreach (var item in myList)
{
myValue = item;
Thread.Sleep(1000);
}
}
The actual thing is I want it to read this values even if I close the form. I presume I would need to assign a Task in order to do this, but I was wandering if there is any better way of doing it since it's a MVC application?
Here's another way to do it:
use AJAX and setTimeout
declare one action in your controller (this one will return your different values)
an integer in your ViewBag, some like: ViewBag.totalItems
Declare an action in your controller: This is important, because this will be your connection with your database, or data. This action will receive the itemIndex and will return that item. Something like this:
[HttpPost]
public JsonResult GetItem(int index) {
return Json(myList.ElementAt(index));
}
ViewBag.TotalItems: Your view has to know how many Items you have in your list. I recommend you to pass that value as an integer via ViewBag:
public ActionResult Index() {
ViewBag.TotalItems = myList.Count();
return View();
}
AJAX and setTimeout: Once that you have all of this, you're ready to update your view without refreshing:
<script>
$(function() {
var totalItems = #Html.Raw(Json.Encode(ViewBag.TotalItems));
var currentItemIndex = 0;
var getData = function() {
$.post("#Url.Action("GetItem")", {index:currentItemIndex}, function(data) {
// data is myList.ElementAt(index)
// do something with it
}).always(function() {
currentItemIndex++;
if(currentItemIndex < totalItems) {
setTimeout(getData, 1000); // get the next item every 1 sec
}
})
}
getData(); // start updating
})
</script>
Your best bet as #DavidTansey mentioned is to use SignlarR. It wraps web sockets and falls back to long polling/etc if the users' browser doesn't support it. Your users will subscribe to specific channels and then you can raise events in those channels.
With regard to your business logic, you'll need to look into async programming techniques. Once you start on this, you'll probably have more specific questions.