I'm trying to make a collection of objects, in this case "AuditPoint" and I want to verify that the list contains the objects I expect it to. I've tried the standard
#foreach (var item in Model)
{
<p>#Html.DisplayFor(m => item)</p>
}
that is normally recommended for this situation but I couldn't get it to display my list. I would really like to double check the contents of my list because I'm picking up this project after my predecessor so I'm lost on a lot of the logic behind his code. So outputting as much as possible to see what I'm doing is always nice but is sometimes difficult due to the intricacy of his program I find it difficult to connect my C# logic to the web logic (which includes html, javascript, typescript, jquery, mustache and I'm sure many other frameworks I have yet to find).
Below is where I am making my collection which I assume is being done right but if you can see anything obviously wrong please point it out. I thought I would include this because it help with anyone's solution.
[HttpPost]
[Route("api/auditpoints")]
public void Post([FromBody]AuditPoint auditPoint)
{
AuditPoints auditPoints = new AuditPoints();
auditPoints.Add(auditPoint);
auditPoints.Save();
//These are my additions below
List<AuditPoint> autoPoints = new List<AuditPoint>();
if (auditPoint.Automated == true)
{
autoPoints.Add(auditPoint);
}
}
Try something like this,
List<AuditPoint> autoPoints = new List<AuditPoint>();
[HttpPost]
[Route("api/auditpoints")]
public void Post([FromBody]AuditPoint auditPoint)
{
AuditPoints auditPoints = new AuditPoints();
auditPoints.Add(auditPoint);
auditPoints.Save();
ListAutoPoints(auditPoint);
}
public void ListAutoPoints(AuditPoint _autoPoint) {
if (auditPoint.Automated == true)
{
autoPoints.Add(auditPoint);
}
}
I think the listing of the items need be separate from the Post logic, then called into existing or new Post method in the controller.
Also, I think, the model needs to be used to populate the list...
#Model IEnumerable<namespace.Models.AutoPoint>
#foreach (var item in Model)
{
<div>
<ul>
<li>#Html.DisplayNameFor(model => model.AutoPoint)</li>
</ul>
</div>
}
Model should fit something like this,
[Table("AuditPoints")
public class AuditPoint
{
[Key]
public AuditPointID { get; set;}
public List<AutoPoint> AutoPoints { get; set;}
}
public class AutoPoint
{
public AutoPointID { get; set; }
public Name { get; set; }
}
Similar answer here: Display List in a View MVC
It isn't a direct answer to the question I posted here but I did solve the problem that this question was related to on my project so I am no longer seeking an answer.
Related
I'm trying to create a simple nav menu, that automatically populates with links to pages matching a specific query (such as subdirectory or name contains x etc..).
I can get the basic path of every indexed page using:
HashSet<string> pages2 = new HashSet<string>();
foreach (var endpoint in endpointDataSource.Endpoints)
{
foreach (var metadata in endpoint.Metadata)
{
if (metadata is PageRouteMetadata)
{
pages2.Add(((PageRouteMetadata)metadata).PageRoute);
}
}
}
which I got from a similar question here: https://stackoverflow.com/questions/66972707/how-to-you-list-of-all-pages-in-a-net-razor-pages-application
But I don't fully understand what's happening here. What is in "endpointDataSource.Endpoints", where is it defined? And what is the "PageRouteMetadata" type etc... It also seems you can't use "endpointDataSource.Endpoints" outside of the .cshtml document, which seems odd.
Is there a better way to accomplish this? I assumed Razor Pages routing would inherently provide methods for this sort of thing, given it's touted as simplifying routing.
The docs are pretty unhelpful with circular referencing eg.
"DefaultEndpointDataSource Initializes a new instance of the DefaultEndpointDataSource class."
"DefaultEndpointDataSource Class Provides a collection of Endpoint instances."
"Endpoint Class Derived Microsoft.AspNetCore.Routing.RouteEndpoint"
I'm sure that's incredibly useful to a .NET and MVC expert. But for me its just a bunch of terms that link to each other in a circle without ever explaining their purpose or use case.
I also tried asking on the .NET subreddit. But they pretty much just tried to sell me CMS services and claimed it can't be or shouldn't be done.
Edit: 27/1/23 What im trying to get working:
enter code here
public class IndexModel : PageModel
{
public readonly IEnumerable<EndpointDataSource> _endpointSources;
public IndexModel(IEnumerable<EndpointDataSource> endpointDataSources)
{
_endpointSources = endpointDataSources;
}
public IEnumerable<RouteEndpoint> EndpointSources { get; set; }
public void OnGet()
{
EndpointSources = _endpointSources
.SelectMany(es => es.Endpoints)
.OfType<RouteEndpoint>();
foreach(var endpointSource in EndpointSources)
{
Console.WriteLine(endpointSource);
Debug.WriteLine(endpointSource.ToString());
JSONTest.endpointStringTest.Add(endpointSource.DisplayName);
}
Console.WriteLine(JSONTest.endpointStringTest);
Debug.WriteLine(JSONTest.endpointStringTest);
}
}
But this results in a null reference. If i understand correctly, this is due to constructors being initialized and deleted before classes are? is there a way to work around this?
But i don't fully understand whats happening here. what is in
"endpointDataSource.Endpoints", where is it defined?
Well,this kind of out of the box scenario, you might get less example on it as of now but it can be solved. If you investigate you would seen endpointDataSource.Endpoints is type of EndpointDataSource which contains list of Endpoints where we can get information related to endpoint/page or route metadata.
EndpointDataSource.Endpoints" outside of the .cshtml document, which
seems odd.
As you can see Endpoints is a type of IReadOnlyList and it's parent class EndpointDataSource is a abstruct class so yes we cannot directly instantiate it but of course we can call it anywhere. Due to its abstruction level we need to inroduce constructor to invoke it.
Is there a better way to accomplish this?
Yes, we do have the better in fact, eligant way to imvoke it to get the all page name therefore, its meta-data as well. Here is the complete demo for you.
Asp.net core Razor Page For Getting All Containing Page Name:
public class ListAllRazorPagesModel : PageModel
{
private readonly IEnumerable<EndpointDataSource> _endpointSources;
public ListAllRazorPagesModel(IEnumerable<EndpointDataSource> endpointDataSources)
{
_endpointSources = endpointDataSources;
}
public IEnumerable<RouteEndpoint> EndpointSources { get; set; }
public void OnGet()
{
EndpointSources = _endpointSources
.SelectMany(es => es.Endpoints)
.OfType<RouteEndpoint>();
}
}
Note: I have injected EndpointDataSource using ListAllRazorPagesModel constructor to invoke Endpoints over it.
View:
#page
#model ListAllRazorPagesModel
<table class="table">
<thead>
<tr>
<th>Display Name
<th>URL
<th>Route Pattern
</tr>
</thead>
<tbody>
#foreach (var pageName in #Model.EndpointSources)
{
<tr>
<td>#pageName.DisplayName?.TrimStart('/')</td>
<td>#pageName</td>
<td>#pageName.RoutePattern.RawText</td>
</tr>
}
</tbody>
</table>
Output:
I am learning asp.net mvc , using visual studio community 2017, and as a sort of teaching project I am making a web app that keeps track of exercise work outs. My model consists of WorkOut objects that have a list (or ICollection more specifically) of Exercise, and each Exercise has an ICollection. Heres the basics of my model classes.
public class WorkOut
{
public int Id { get; set; }
public int Length { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Exercise> ExerciseList { get; set; }
}
public class Exercise
{
public int Id { get; set; }
public string Name { get; set; }
public int WorkOutId { get; set; }
public virtual WorkOut WorkOut { get; set; }
public virtual ICollection<RepUnit> Sets { get; set; }
}
public class RepUnit
{
public int Id { get; set; }
public int Rep { get; set; }
public int? Weight { get; set; }
public int ExerciseId { get; set; }
public virtual Exercise Exercise { get; set; }
}
Generating a view automatically with WorkOut as a model leads to Create action and view that only generates a Length and Date property. In general, auto generated view and controllers only add the non virtual properties. So I figure maybe I have to do a multistep creation process; Create a workout, create an exercise and add reps to it, add that exercise to the work out, either stop or add another exercise. So I figured Id let VS to some of the work for me, and I make controllers and views for each of the model object typers (WorkOutsController, ExercisesController, RepUnitsController), and later I would trim out the uneeded views or even refactor the actions i actually use into a new controller.
So WorkOutsController my POST action is this.
public ActionResult Create([Bind(Include = "Id,Length,Date")] WorkOut workOut)
{
if (ModelState.IsValid)
{
db.WorkOuts.Add(workOut);
db.SaveChanges();
return RedirectToAction("Create","Exercises",new { workoutId = workOut.Id });
}
return View(workOut);
}
So I carry the workoutId to the Exercise controller but this is where I am unsure how to proceed. I want to keep carrying around the workoutId and for the next step, where I give the exercise a name, also show the associated date that was just added. The only thing I could think to do was instantiate an Exercise in the GET action of ExerciseController like so.
public ActionResult Create(int workoutID)
{
Exercise ex= new Exercise();
ex.WorkOutId=workoutID;
ex.WorkOut=db.WorkOuts(workoutID);
return View(ex);
}
This seems terrible and I've not seen anything like this done in any examples, but it seems to work. The same exercise object is brought back to my POST create action here
public ActionResult Create([Bind(Include = "Id,Name,WorkOutId")] Exercise exercise)
{
if (ModelState.IsValid)
{
db.Exercises.Add(exercise);
db.SaveChanges();
return RedirectToAction("Create", "RepUnits", new { exerciseId = exercise.Id });
}
return View(exercise);
}
which as you see calls the RepUnits controller and associated Create action. There I do something very similar; create a rep object and pass it to the view, and essentially I add reps until I'm done. Eventually I will create navigation to either go back to add a new exercise or go back to an Index view.
So to sum up, it seems wasteful to be passing entire objects around, and maybe my whole implementation is wrong and I should be trying to somehow do this all on one form. Up to this point googling hasnt found me much because I wasnt sure what questions to be asking, however this post Creation of objects using form data within an ASP.NET MVC application just popped up in the similar question dialogue and the app in question is coincidentally very similar! However when the OP mentions passing the workoutId around, how is this accomplised? I thought to maybe use the ViewBag but how do I get the view to handle this Id?
I had though to try, as an example
public ActionResult Create(int workoutId)
{
ViewBag.WoID = workoutId;
return View();
}
in the ExercisesController and then in the associated Create view:
#Html.Hidden("WorkOutId", new { ViewBag.WoID })
But later in the view when I try to reference the workout date it comes up blank
<div class="form-group">
#Html.LabelFor(model => model.WorkOut.Date, "Work Out On:", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DisplayFor(model=>model.WorkOut.Date)
</div>
</div>
Should I be doing something like this in the view:
#Model.WorkOutId=ViewBag.WoID;
which doesnt work for some reason (Compiler Error Message: CS1525: Invalid expression term '='), but is that along the lines of how I pass these ids around?
The scaffolded views are intentionally simplistic. Dealing with related items requires multiple considerations, and Visual Studio won't make those for you. However, you can and are very encouraged to alter the scaffolded views to your particular needs.
To create exercises in the same view as your workout for example, you need only generate fields for Exercise with names that will allow the modelbinder to bind the posted data. For collection properties that means something like CollectionProperty[N].Property, where N is an index.
For example, you can initialize your workout with three exercises:
var model = new Workout
{
ExerciseList = new List<Exercise>
{
new Exercise(),
new Exercise(),
new Exercise()
}
};
Then, in your view:
#for (var i = 0; i < Model.Exercises.Count(); i++)
{
#Html.EditorFor(m => m.ExerciseList[i].Name)
}
However, there's one thing to note here: ICollection is not indexable. As a result, to do it this way, you'd need a property typed as List<Exercise>. This is where view models come in very handy. Nevertheless, there is a way around this: you can use EditorFor on the Excercises collection instead. For example, the above code would be reduced to just:
#Html.EditorFor(m => m.ExerciseList)
EditorFor is a "templated helper", which means simply that it uses templates to render what's passed to it. Thankfully, it has some defaults, so to a point, you don't need to worry about that, but it become problematic. For example, here, Razor will simply iterate over the items in ExerciseList and render the template for Exercise for each. Since Exercise is a custom type and doesn't have a default template, it will then introspect the class and render a template for each public property on Exercise. Sometimes this works just fine. For example, Name will be rendered with a text box, as it should be. However, you'll also get text boxes for Id and WorkoutId, which you wouldn't want to even be visible on your form.
You can solve this issue by creating your own editor template for Exercise, by adding a view to Views\Shared\EditorTemplates\Exercise.cshtml. This view would have a model of Exercise, then, and would simply include a text box for your name property. Then, when you use EditorFor on ExerciseList as above, it will render each Exercise utilizing that view.
With all that out of the way, though, you've likely realized that this is still somewhat limiting: you have to initialize with a certain number of exercises and then that's all you get. That's where JavaScript comes in. Instead of iterating over a predefined list of exercises, you can simply dynamically add a new block of exercise fields as needed (or remove existing blocks). However, writing JavaScript for this task manually would be very painstaking and dense. At this point, you're better off utilizing something like Knockout.js, Angular, or similar. These libraries, among other things, give you two-way databinding, so you could simply set up a JavaScript template for what a block of exercise fields will look like, and then bind that to an ExceriseList member of a JavaScript object (your client-side "view model"). You could then cause these fields to repeat simply by adding or removing items from this JS array. Obviously, there's much more that goes into this, but that's the basic framework. You'd need to consult the individual documentation of the library you went with to determine exactly how to set everything up.
You can then rinse and repeat all this for other levels of relationships as well. In other words, it's entirely possible to post this entire object graph of a workout with multiple exercises, each with multiple rep units, etc. all in one go with one view.
I am a newbie and creating a website where you can create your own custom quizes. Ive made a database that stores a class object mytests that consists of a name, and a list of questions parameter.
public class MyTests
{
public int ID { get; set; }
public string name { get; set; }
public string description { get; set; }
public List<MyQuestions> AllTestQuestions;
}
//using this object for questions
public class MyQuestions
{
public string QuestionDescription { get; set; }
public string MultipleChoiceCorrect { get; set; }
public string MultipleChoiceB { get; set; }
public string MultipleChoiceC { get; set; }
public string MultipleChoiceD { get; set; }
public string Answerexplanation { get; set; }
}
I'm using the default database code generated by visual studio. I have no problem adding this test object(mytest) to the database, but what I want to do is that on the edit.cshtml view I want to be able to add elements to the question list before returning the object to the database saved.
The problem is I don't know how to edit the model object from the view, or if this is even possible. I could maybe get it to work through a redirect? but I thought that adding the elements directly from the view would be easier. Is it possible to modify the model.object inside a view from the view (putting security concerns aside)?
For example model.title = something;
or
model.list.add()
Is anything like this possible?
If this question is not clear please let me know and I will try to clarify in the comments.
Yes, it is possible to edit the model from within the view.
From within your .cshtml file specify the view model using the #model declaration, then edit the model like so:
#model Namespace.For.MyTests
#Model.name = "Hello World";
<p>#Model.name</p>
Whilst this would work, it's not really what the view is for so I wouldn't recommend it.
The view is about presenting your data, not mutating it - that should be done in the controller, or domain layer. As soon as the user leaves the page then your changes will be lost due to the stateless nature of the web (.NET MVC passes data to the view from the controller, then ends the request).
This should be done at the controller level. You could do it on a view but it's not what the view is for.
Your issue is that if the page is refreshed you will lose you content, so if you do anticipate on the page refreshing you will need a way in which to temporarily hold the information before it being saved.
On a side note, I'd also consider renaming your classes "MyTests" to "MyTest" (singular) and "MyQuestions" to "MyQuestion"... it's just good practice because then you'd have a List of singleton "MyQuestion" in a "MyTest". EntityFramework Codefirst will pluralise the names when the database is created/update.
I am fairly new to MVC, but have quite a bit of experience in development in general, and am having an issue with MVC request life cycle it seems.
Will try to keep this simple, even tho the project is a bit complex in some areas.
I have a view bound to a view model that has a few complex list properties. These properties are displayed via checkboxes who's IDs are not directly related to any property in the model, but instead related to the IDs of the objects in the List<>. Because of this, the checked values do not automatically get applied to the model on POST.
To get around that, I added code in the Action method in the controller that parses the proper controls (in the Request.Form collection) and assigns the checked/selected value to the proper list items in the model.
This works perfectly up to a point.
Now, I also use Fluent Validation, and the problem is when performing custom validation rules when posting a new model to the server. The Validation routine is firing BEFORE the controller's action method, and thus before my processing of the list objects.
So, my question is, is there a way I can override the initial call to the model validation so I can just call the validation manually after my processing? I know I can do that which will fix the problem without overriding the initial call, but some of the validation takes a bit of time to process since it requires linq queries to a live database, so I do not want the validation to fire 2 times - that will quite literally double the time it takes to return no matter if the model is valid or not.
EDIT: Adding a example:
namespace Models
{
[Validator(typeof(MemberValidator))]
public class ViewMember
{
public int MemberID { get; set; }
public short RegionID { get; set; }
public List<PropTypeInfo> PropTypes { get; set; }
}
}
PropTypeInfo class:
public class PropTypeInfo
{
public byte ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Selected { get; set; }
public PropTypeInfo(byte i, string n, string d, bool sel)
{
ID = i;
Name = n;
Description = d;
Selected = sel;
}
public static List<PropTypeInfo> GetAll(bool selected = false)
{
List<PropTypeInfo> output = new List<PropTypeInfo>();
OpenAccess.Context context = new OpenAccess.Context();
var list = (from f in context.Prop_Types orderby f.PropType select f).ToList();
foreach (OpenAccess.WebrentzServerPayments.Models.Prop_Type p in list)
output.Add(new PropTypeInfo(p.PropType, p.PropName, p.DisplayText, selected));
return output;
}
}
now here is the code in the view that renders the checkboxes for each item in the list:
<div class="Column Emp-PropTypes">
#foreach (WebrentzServerPayments.Models.PropTypeInfo ptype in Model.PropTypes)
{
<div style="float:right;width:20%;font-weight:bold;">
#Html.CheckBox("ptype_" + ptype.ID, ptype.Selected, new {Value=ptype.ID}) #Html.Raw(" ") #ptype.Name
</div>
}
</div>
And here is the code I use in the Controller Action method to pull that data back in to the List:
foreach (PropTypeInfo info in member.PropTypes)
info.Selected = form[string.Format("ptype_{0}", info.ID)].Contains(info.ID.ToString());
As a little background, a "PropType" is a type of property (house, condo, apartment) - there are about 2 dozen of them, and more can be added/removed at any time. The list in the class called "PropTypes" is first populated with the Name, Description and ID from a table in the database that lists all the available proptypes for that region.
We then will mark the proptypes as "selected" if the user has chosen that particular type. Those are saved to a table called Member.PropTypes (MemberID, ProptypeID).
So, at runtime the list will contain one record for each available proptype and the selected property will be set to yes if that user has selected it. That makes it easy to render the full list in the view...
Its actually quite a bit more complex as there are almost a dozen such lists, but each works the exact same way just with different data, as well as about 200 additional properties that are easier to manage. Only these lists are causing the issue.
Any help appreciated!
Dave
I'm looking for a way to achieve the following in MVC 3.
Let's say I have a page with one question. On a post, I would like to bind the following ViewModel:
public class QuestionElementViewModel
{
public int QuestionId { get; set; }
public string Name { get ; set; }
public string Question { get; set; }
public string Feedback { get; set; }
}
This can easily be done like this (if I use the correct names in the View):
[HttpPost]
public ActionResult Index(QuestionElementViewModel pm)
{
//Do something
}
Now I have multiple questions on my page. Using the technique explained here: http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx I can also make this quite easy:
[HttpPost]
public ActionResult Index(QuestionElementViewModel[] pm)
{
//Do Something
}
But lets say I don't have only questions, but different elements on my page and these elements can vary. Would it be somehow possible to achieve something like this:
[HttpPost]
public ActionResult Index(IElementViewModel[] pm)
{
//Do Something
}
where every ViewModel that implements this interface is automatically bound?
I've tried this code and it results in an error: Cannot create instance of an interface, which sounds pretty obvious.
I think i should create a custom model-binder, but I'm not very familiar with that and I don't want to step away from the standard MVC-framework too much..
You will need a custom model binder for this scenario because you are using an interface and the default model binder wouldn't know which implementation to instantiate. So one possible technique is to use a hidden field containing the concrete type for each element. Here's an example that might put you on the right track.