Preserve / retrieve model (list) data - Razor Pages - c#

My problem is probably something obvious or simple but I can't seem to figure it out.
I have a button that when pressed it retrieves JSON data from an API endpoint (from another company). This endpoint returns over 30000 records.
When the data has been retrieved I am showing it to the fron-end with the proper Model. This all works fine.
I added a search box which is handled server side, when a search value is given it returns to the same endpoint which retrieves the data, however if the data has already been loaded, I dont want to retrieve the data again. So I thought I could just use the Data property model to check whether it is empty. This however does not work.
The code looks as follows:
Backend razor page:
public class IndexModel : PageModel
{
private readonly IGetDataService _getDataService;
[BindProperty]
public List<DataModel> DataModel { get; set; }
public string CurrentFilter { get; set; }
public IndexModel(IGetDataService dataService)
{
_getDataService = dataService;
}
public async Task<IActionResult> OnGetLoadData(string searchString)
{
CurrentFilter = searchString;
List<DataModel> data = null;
// Check if data has already been retrieved
if (DataModel == null)
data = await _getDataService.ReadAll(new Uri("https://restapi.xxxxx"));
else
data = DataModel;
if (!String.IsNullOrEmpty(searchString))
{
data.Where(s =>
s.field1.Contains(searchString) ||
s.field2.Contains(searchString) ||
s.field3.Contains(searchString) ||
s.field4.Contains(searchString)
);
}
DataModel = data;
return Page();
}
}
Front-end code:
#page "{handler?}"
#model IndexModel
#{
ViewData["Title"] = "Data page";
}
<form asp-page-handler="LoadData" asp-route-id="" method="get">
<button id="lurvink-button" class="btn btn-primary">
Get Data
</button>
</form>
#if (Model.DataModel != null)
{
<form class="form-group has-search" asp-page-handler="LoadData" method="get">
<span class="fa fa-search form-control-feedback"></span>
<input type="text" class="form-control" placeholder="Search" name="SearchString" value="#Model.CurrentFilter">
</form>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.DataModel[0].field1)
</th>
<th>
#Html.DisplayNameFor(model => model.DataModel[0].field2)
</th>
<th>
#Html.DisplayNameFor(model => model.DataModel[0].field3)
</th>
<th>
#Html.DisplayNameFor(model => model.DataModel[0].field4)
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.DataModel)
{
<tr>
<td>#item.field1</td>
<td>#item.field2</td>
<td>#item.field3</td>
<td>#item.field4</td>
</tr>
}
</tbody>
</table>
}

I am not familiar with Razor Pages, but expect (like with normal MVC) the state is not maintained over multiple calls.
You could use MemoryCache to store the DataModel for a certain time.

Related

HttpPost returns null Model in ASP.NET MVC

Advance warning, I am extremely new to ASP.NET.
I'm working on a project which will display rows of data from a db table. When a user clicks the "Ignore" button next to a row, it should update the corresponding "Ignore" column on that row with true in the database.
The view itself works fine, it displays all the data as expected. But when "Ignore" is clicked, and it calls the Ignore() method on the controller, the model is which is passed to the controller is null.
My model, generated by entity framework (with extraneous properties removed):
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IgnoreDailyItems.Models
{
[Table("DataChecks.tbl.DailyItems")]
public partial class DataChecksTblDailyItems
{
[Column("entryId")]
public int EntryId { get; set; }
[Column("ignore")]
public bool? Ignore { get; set; }
}
}
The view:
#model IEnumerable<IgnoreDailyItems.Models.DataChecksTblDailyItems>
#{
ViewBag.Title = "Placeholder";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.EntryId)
</th>
</tr>
#{ var item = Model.ToList(); }
#for(int i = 0; i < Model.Count(); i++)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item[i].EntryId)
</td>
<td>
#using (Html.BeginForm("Ignore", "Home", FormMethod.Post))
{
#Html.HiddenFor(modelItem => item[i].EntryId)
<button type="submit" class="btn btn-danger">Ignore</button>
}
</td>
</tr>
}
</table>
And the Ignore() method on the controller:
[HttpPost]
public ActionResult Ignore(DataChecksTblDailyItems modelData)
{
using (var context = new IgnoreDailyItemsContext())
{
var query = context.DataChecksTblDailyItems
.Where(b => b.EntryId.Equals(modelData.EntryId));
foreach (var q in query)
{
q.Ignore = true;
}
context.SaveChanges();
return RedirectToAction("Index", "Home");
}
}
You're generating the form in wrong way.
#Html.HiddenFor(modelItem => item[i].EntryId)
It will generate an input hidden with item[0].EntryId, item[1].EntryId... as name/id for each row in the table, for that reason the post model definition does not match.
To solve it, set the input hidden name manually:
#Html.Hidden("EntryId", item[i].EntryId)
You need to pass IEnumerable<IEnumerable> as a parameter.
public ActionResult Ignore(IEnumerable<DataChecksTblDailyItems> modelData)
{

How to use Github API on a ASP.NET MVC app?

I'm new in ASP.NET MVC world and I've been trying to get more familiar with the .NET plataform and its particularities. Currently, I am trying to make a C# MVC app that uses the Github API to retrieve some data like list all my repositories, show some of their info, search for repositories by name (not necessarily mine) etc.
But I'm struggling on simple things. First I don't know how to make a correct connection to get these information. I've tried following the octokit tutorial on their page and tried to retrieve data using JSON and convert to a C# object but all my attempts failed on both.
I saw this example in octokit, it works for console apps:
var github = new GitHubClient(new ProductHeaderValue("MyAmazingApp"));
var user = await github.User.Get("half-ogre");
Console.WriteLine(user.Followers + " folks love the half ogre!");
But I am confused where I should put these, considering the MVC structure on ASP.NET, or how it should be different. I didn't find octokit documentation very helpful on those aspects.
And some of my things I've done so far trying to deserialize JSON to C#:
Models
namespace GithubAPI.Models {
public class Repositories {
public string name { get; set; }
public string language { get; set; }
public string owner { get; set; } //or login ?
public string updated_at { get; set; }
}
public class RepCollections {
private List<Repositories> repositories;
public List<Repositories> Repositories { get => this.repositories; set => this.repositories = value; }
}
}
I did this when I tried to save JSON data to a Class
Controllers
public ActionResult Index() {
var url = "https://api.github.com/users/{myuser}/repos";
using (var webClient = new WebClient()) {
var rawJSON = string.Empty;
try {
rawJSON = webClient.DownloadString(url);
}
catch (Exception) { }
RepCollections rep = JsonConvert.DeserializeObject<RepCollections>(rawJSON);
return View();
}
}
It didn't work, probably because it's in the wrong place, idk.
View
#using GithubAPI.Models
#model RepCollections
#{
ViewBag.Title = "Repositories";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="row">
<div class="col-md-4">
<h2>Repositories</h2>
<br/>
<p>User: #Html.DisplayNameFor(model => model.owner)</p>
<div>
<table class="table">
<tbody>
<tr><th></th></tr>
#foreach (Repositories r in Model) {
<tr>
<td>
#r.name
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
Needless to say the foreach didn't work.
Can someone give me some light, please? Thanks.
P.S.: I not using OAuth. Just want a basic authentication.
Your foreach loop is not working because you are not passing the model from Controller to the View. Try this:
Controller
public ActionResult Index()
{
var url = "https://api.github.com/users/{myuser}/repos";
using (var webClient = new WebClient())
{
var rawJSON = webClient.DownloadString(url);
RepCollections rep = JsonConvert.DeserializeObject<RepCollections>(rawJSON);
return View(rep);
}
}
View
#using GithubAPI.Models
#model RepCollections
#{
ViewBag.Title = "Repositories";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="row">
<div class="col-md-4">
<h2>Repositories</h2>
<br />
<p>User: #Html.DisplayNameFor(model => model.owner)</p>
<div>
<table class="table">
<tbody>
<tr><th></th></tr>
#foreach (Repositories r in Model)
{
<tr>
<td>
#r.name
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>

Mass edit ISingleResult

I got a stored procedure which i show in the view for edits. I made a strong type of the stored procedure. When i edit the fields and then press the save button, the parameter "cm" is always empty. And it's not showing a list but just 1 record.
The custom model:
public class CustomModel
{
public string Description { get; set; }
public System.Data.Linq.ISingleResult<GetItems_ListResult> ItemList { get; set;}
}
This part of the controller sends it to the view:
public ActionResult Details(int id)
{
var row = dataContext.Items.FirstOrDefault(x => x.ItemID == id);
var cm = new CustomModel();
cm.ItemList = dataContext.GetItem_List(row);
cm.Description = row.Description;
return View(cm);
}
This controller receives data from the view:
[HttpPost]
public ActionResult UpdateItems(CustomModel cm)
{
return RedirectToAction("Index");
}
This is the view:
#model TestWeb.Models.CustomModel
#using (Html.BeginForm("UpdateItems", "Item", FormMethod.Post))
{
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
#foreach (var p in Model.ItemList.ToList())
{
<tr>
<td>
#Html.HiddenFor(mdl => p.ItemId)
</td>
<td>#p.Name</td>
<td>
#Html.EditorFor(mdl => p.Description)
</td>
</tr>
}
</table>
<p>
<input type="submit" value="save" />
</p>
}
What am i doing wrong here?
Try the following:
Make a GetItems_ListResult.cshtml like this:
<tr>
<td>
#Html.HiddenFor(mdl => mdl.ItemId)
</td>
<td>#Model.Name</td>
<td>
#Html.EditorFor(mdl => mdl.Description)
</td>
</tr>
Then in your for loop do this:
#for (int i = 0; i < Model.ItemList.Count(); i++)
{
#Html.EditorFor(m => m.ItemsList[i])
}
Update: I didn't quite notice you were using an ISingleResult. You could do this instead:
//Since it'll have none or one element..
if(Model.ItemList != null && Model.ItemList.Any())
{
#Html.EditorFor(m => m.ItemList.First())
}
Have you read this blog post? http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/ Steve covers editing lists in asp mvc.
Once your read that take a look at this nuget package http://nuget.org/packages/BeginCollectionItem

Postback problems

When I postback on my "Review" page I check the modelstate and based on the results either redisplay the page or continue. I have two issues.
When it fails validation it hangs up on partial views on that it loaded fine on the original get for the page.
edit: This was caused by the [HttpGet]attribute being applied to methods for the partials views. I removed the attribute and the partials renedered.
If I comment out the partials then the the whole page diplays without any CSS styles..it's all text black and white. Edit: I'm still having the problem with the styles missing from the page
[HttpGet]
public ActionResult Review()
{
var agmtsService = new AgreementsService();
bool? scoreRelease = agmtsService.GetReleaseScoreIndicator();
var vm = new ReviewModel {ReleaseScoreIndicator = scoreRelease};
return View(vm);
}
[HttpPost]
public ActionResult Review(ReviewModel model)
{
if(!ModelState.IsValid)
{
return View(model);`**This is displaying the view w/o head section and no css**`
}
return RedirectToAction("CheckOut", "Financial");
}
edit:
View Model
public class ReviewModel
{
public bool? ReleaseScoreIndicator { get; set; }
// Terms & Conditions
[RequiredToBeTrue(ErrorMessage = "Eligibility Checkbox must be checked.")]
public bool TermsEligibility { get; set; }
[RequiredToBeTrue(ErrorMessage = "True and Accurate Checkbox must be checked.")]
public bool TermsAccurate { get; set; }
[RequiredToBeTrue(ErrorMessage = "Identity Release Checkbox must be checked.")]
public bool TermsIdentityRelease { get; set; }
[RequiredToBeTrue(ErrorMessage = "Score Release Checkbox must be checked.")]
public bool TermsScoreRelease { get; set; }
}
public class RequiredToBeTrueAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
return value != null && (bool)value;
}
}
View
#model Registration.Web.Models.ReviewModel
#{
ViewBag.DisableNavigation = true;
}
<script type="text/javascript">
$(document).ready(function () {
$('.open_review').toggle(function() {
$(this).text('Done').parents('.review_section').addClass('open_for_review').find('.review_content').slideDown('fast');
return false;
}, function() {
$(this).text('Review').parents('.review_section').removeClass('open_for_review').find('.review_content').slideUp('fast');
return false;
});
});
</script>
<div class='section module'>
<h2>
Please Review Your Application
</h2>
<p>
Remember that your application fee is
<strong>
not refundable.
</strong>
Review your information below and make corrections before submitting.
</p>
<div class='review_section'>
<a class="button open_review" href="#">Review</a>
<h4>
Identification
</h4>
#{Html.RenderAction("Review", "PersonalInformation");}
</div>
<div class='review_section'>
<a class="button open_review" href="#">Review</a>
<h4>
Education
</h4>
#{Html.RenderAction("Review", "MedicalEducation");} /////hangs here
</div>
<div class='review_section'>
<a class="button open_review" href="#">Review</a>
#{Html.RenderAction("Review", "PostGraduate");}////then hangs here
</div>
</div>
<div class='actions' id='terms_and_conditions'>
#using (Html.BeginForm("Review", "Agreements", FormMethod.Post))
{
//"reviewForm","Agreements", FormMethod.Post
#Html.ValidationSummary(true)
<div class='group' id='data_release'>
<h4>
Data Release
</h4>
<p>
Do you wish to release your scores?
</p>
<ul class='input_group'>
<li>
#Html.RadioButtonFor(model => model.ReleaseScoreIndicator, true)
<label>
Yes
</label>
</li>
<li>
#Html.RadioButtonFor(model => model.ReleaseScoreIndicator, false)
<label>
No
</label>
</li>
</ul>
</div>
<div class='group' id='terms'>
<h4>
Terms & Conditions
</h4>
#Html.ValidationSummary(false)
<table>
<tbody>
<tr>
<th>
#Html.CheckBoxFor(x => x.TermsEligibility)
#* #Html.CheckBox("terms_eligibility")*#
</th>
<td>
<label for='terms_eligibility'>
I currently meet all of the
requirements
and have read the
Information
</label>
</td>
</tr>
<tr>
<th>
#Html.CheckBoxFor(x => x.TermsAccurate)
#* #Html.CheckBox("terms_accurate")*#
</th>
<td>
<label for='terms_accurate'>
The information I've provided is true and accurate
</label>
</td>
</tr>
<tr>
<th>
#Html.CheckBoxFor(x => x.TermsIdentityRelease)
#* #Html.CheckBox("terms_identity_release")*#
</th>
<td>
<label for='terms_identity_release'>
I authorize the release
</label>
</td>
</tr>
<tr>
<th>
#Html.CheckBoxFor(x => x.TermsScoreRelease)
#*#Html.CheckBox("terms_score_release")*#
</th>
<td>
<label for='terms_score_release'>
I agree
</label>
</td>
</tr>
</tbody>
</table>
</div>
<div class='actions'>
<input type="submit" value="Go To Checkout" class="button" />
<a class="button" onclick="getForMasterPage('#Url.Action("CheckOut", "Financial")', null);">BYPASS</a>
</div>
} </div>
Do you need to set the ReleaseScoreIndicator on the return from being posted? It looks like it gets set for the initial GET, but the subsequent one, it doesn't get set. Does the view use that property?

MVC 3 Editor Template with Dynamic Drop Down

How do I get the drop down to display as part of my editor template?
So I have a Users entity and a Roles entity. The Roles are passed to the view as a SelectList and User as, well, a User. The SelectList becomes a drop down with the correct ID selected and everything thanks to this sample.
I'm trying to get an all-in-one nicely bundled EditorTemplate for my entities using MVC 3 so that I can just call EditorForModel and get the fields laid out nicely with a drop down added whenever I have a foreign key for things like Roles, in this particular instance.
My EditorTemlates\User.cshtml (dynamically generating the layout based on ViewData):
<table style="width: 100%;">
#{
int i = 0;
int numOfColumns = 3;
foreach (var prop in ViewData.ModelMetadata.Properties
.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Display(prop.PropertyName)
}
else
{
if (i % numOfColumns == 0)
{
#Html.Raw("<tr>");
}
<td class="editor-label">
#Html.Label(prop.PropertyName)
</td>
<td class="editor-field">
#Html.Editor(prop.PropertyName)
<span class="error">#Html.ValidationMessage(prop.PropertyName,"*")</span>
</td>
if (i % numOfColumns == numOfColumns - 1)
{
#Html.Raw("</tr>");
}
i++;
}
}
}
</table>
On the View I'm then binding the SelectList seperately, and I want to do it as part of the template.
My Model:
public class SecurityEditModel
{
[ScaffoldColumn(false)]
public SelectList roleList { get; set; }
public User currentUser { get; set; }
}
My Controller:
public ViewResult Edit(int id)
{
User user = repository.Users.FirstOrDefault(c => c.ID == id);
var viewModel = new SecurityEditModel
{
currentUser = user,
roleList = new SelectList(repository.Roles.Where(r => r.Enabled == true).ToList(), "ID", "RoleName")
};
return View(viewModel);
}
My View:
#model Nina.WebUI.Models.SecurityEditModel
#{
ViewBag.Title = "Edit";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Edit</h2>
#using(Html.BeginForm("Edit", "Security"))
{
#Html.EditorFor(m => m.currentUser)
<table style="width: 100%;">
<tr>
<td class="editor-label">
User Role:
</td>
<td class="editor-field">
<!-- I want to move this to the EditorTemplate -->
#Html.DropDownListFor(model => model.currentUser.RoleID, Model.roleList)
</td>
</tr>
</table>
<div class="editor-row">
<div class="editor-label">
</div>
<div class="editor-field">
</div>
</div>
<div class="editor-row"> </div>
<div style="text-align: center;">
<input type="submit" value="Save"/>
<input type="button" value="Cancel" onclick="location.href='#Url.Action("List", "Clients")'"/>
</div>
}
Hopefully that's clear enough, let me know if you could use more clarification. Thanks in advance!
Since you need access to the SelectList you can either create an editor template bound to SecurityEditModel or you can pass the SelectList in ViewData. Personally I would go with the strongly typed approach.

Categories

Resources