IEnumerable Model and Create Field on the Same Page - c#

(This post is candy-obfuscated; obfuscated because I have to, candy for the lulz. Trust me, the real thing is actually worthwhile.)
Using ASP.NET MVC 4 and EF 5, I'm trying to create a page that simultaneously shows a list of entities that currently exist in the database and a simple create field at the bottom. I have a functioning way of doing it, but I'm wondering if there's a better way, because my current one feels very roundabout. I'd upload an image of what I've got, but I have to have at least ten reputation, so... on to the post.
The model I'm passing in looks like so:
public class CandyBrand
{
public int ID { get; set; }
[Required(ErrorMessage = "Brand name is required.")]
[Display(Name = "Brand Name")]
public string Name { get; set; }
}
The controller looks like this, including both GET and POST methods:
public ActionResult CandyBrands()
{
return View(context.CandyBrands);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CandyBrands(CandyBrand brand)
{
if (ModelState.IsValid)
{
context.CandyBrands.Add(brand);
context.SaveChanges(); //try/catch block removed for brevity
}
return View(db.CandyBrands);
}
And my view:
#model IEnumerable<CandyDatabase.Models.CandyBrands>
#{
ViewBag.Title = "Brands";
}
<h2>Brands</h2>
<p>#Html.DisplayNameFor(m => m.Name)</p>
#foreach (var brand in Model)
{
<p>#Html.DisplayFor(m => brand.Name)</p>
}
<h3>Create New</h3>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<p>#Html.EditorFor(m => m.FirstOrDefault().Name) <input type="submit" value="Create" /></p>
<p>#Html.ValidationMessageFor(m => m.FirstOrDefault().Name)</p>
}
<p>#Html.ActionLink("Back to Candy List", "Index", "Home")</p>
#section Scripts{
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("/Scripts/clear-inputs.js")
}
Because I'm passing in a list of candy brands, the model takes on the type IEnumerable. This isn't a problem for the first half - the foreach loop takes care of this. However, this creates other problems further down the page. Because the model is an IEnumerable, Html.EditorFor doesn't like it. Currently, to solve this, I'm calling FirstOrDefault, which brings it down to one entry. This is annoying in and of itself, but it doesn't stop there; MVC then automatically (and unwantedly (which may or may not be a word)) fills the editor with the data from the first entity in the model! If you notice at the bottom, there's a call to a 'clear-inputs' script; this script exists SOLELY to fix this problem by running
$("input[type!='submit']").val("");
to clear all the fields when the page loads.
Is there a better way to do this? I'm not above accepting the answer "Use a view model, dangit!", but it seems silly to have a view model that has a CandyBrand entity and then a list of CandyBrand entities.

So, it appears, according to Henk Holterman
A ViewModel is the actual, simple and clear solution here. Nothing silly about it.
and Darin Dimitrov
Dude, use a view model, dangit! How else are you even thinking in being able to write an ASP.NET MVC application that actually works? Without a view model? I have never seen such a beast released in the wild yet. Coz that ain't gonna happen. The very first thing you should start thinking before even writing a single line of code from your application is how your view models are gonna look like.
that the answer is a view model. Thank you both for your help; henceforth, I will use view models, dangit!

Related

Get controller properties in HTML, is everything meant to go in a viewbag?

From what I read in various tutorials, or simply the sample project, the Controller use the ViewBag to store anything that will be dipsplayed on the page.
In the Controller I can just type ViewBag.AnythingIWant = "Foo";, and it'll render in the HTML. Apparently that's what is done in the sample project, at least for title and various texts.
But most of the text is hardcoded in the HTML and obviously I don't want that. Considering I'm not new to C# or MVC in Xamarin (mobile development), I feel like I should grasp this pretty quick, but I don't. Could someone clarify to me the following :
My Controller knows the ViewModel (which does most of the work) and himself uses the Model privately. I'm used (from iOS dev) for the controller to be the last layer of UI, and inside the controller I would just have all my labels and whatever I want, and I can fill them with whatever is available in the ViewModel.
Here, there is this HTML layer that I don't know how to connect to the controller.
I have a strong feeling that putting everything in the ViewBag cannot be the way to go. Could anyone briefly reveal the piece I am missing to use proper objects inside the HTML ?
Razor might be what's confusing me, considering whatever I add publicly in my Controller, I can't find it in the related HTML using #MyProperty
I know this is pretty broad question but I know that I only miss a small piece of knowledge to unlock everything.
As noted in the comments, pass a ViewModel to the View to be rendered!
Controller
public ActionResult Index() {
var viewModel = new MyViewModel { Name = "some string" };
return View("Index", viewModel);
}
Index.cshtml
#model MyViewModel #* Tell Razor which ViewModel to expect *#
#{ string name = Model.Name; }

How to create a complex view model in ASP.NET MVC?

Creating and updating a complex view model is what I struggle with most of the time when I'm working on a web project.
For instance, I got a PageViewModel that takes a background image URL and a page title to pass to _Layout.cshtml, so
#model WebApplication.ViewModels.PageViewModel
<body background-image=#Model.BackgroundImageUrl>
...
</body
As for every other view model I now got 2 options:
Derive from PageViewModel
Create a property on PageViewModel to hold the specific view model (composition over inheritance etc.)
For obvious reasons I'm more inclined towards option 2. In any case, for GET actions I now need to initialise all the properties from PageViewModel as well as the properties from the view model I actually need for a particular action, e.g.
public PageViewModel
{
public string BackgroundImageUrl { get; set; }
public ContactViewModel Contact { get; set; }
}
is created like
public IActionResult Contact(int contactId)
{
...
var viewmodel = new PageViewModel
{
BackgroundImageUrl = ...,
ContactViewModel = new
{
...
}
}
}
which to me is a recipe for disaster.
Things get even worse on POST, because ideally I will post only those fields that are relevant to the action in question, that is
public IActionResult Contact(ContactViewModel viewmodel)
{
if (ModelState.IsValid)
{
... (this is the easy case)
return RedirectToAction(...)
}
... (now we have a problem)
}
If anything goes wrong in the POST action I need to rebuild the entire view model graph, which is not too bad for the above example but in a real world application this gets extremely messy very fast (just think of populating drop down lists with data from a store). Controller actions are supposed to be lean, aren't they? It doesn't feel right to make the view models responsible for retrieving their own data either, so probably I should come up with a view model factory. That in turn will produce a plethora of factories, one for each view model which, again, will get messy.
I'm wondering if there is a better approach.
One possibility to consider is to use a child action responsible for some portion of the layout.
So for example you could have the following child action which will be responsible for populating the background image URL:
[ChildActionOnly]
public ActionResult BackgroundImage()
{
var model = new MyViewModel
{
BackgroundImageUrl = ...
};
return PartialView(model);
}
and then have a corresponding partial view:
#model MyViewModel
<img src="#Model.BackgroundImageUrl" alt="" />
which can be included in your main Layout (which in this case doesn't need a view model because the different portions of it will be assembled from the child actions):
<body>
#Html.Action("BackgroundImage", "SomeController")
...
</body>
Using this approach the main action that is responsible for rendering the view doesn't need to know and assemble a complex view model. It will focus only on its specific view model.

Specifying single method as target for multiple controller URL's

There are lots of questions asking about this logic in reverse, however, I can't find an answer for my issue. I'm new to MVC so might not have the terminology specified correctly.
I have a View containing a form, where a user can request a product. The same View is used regardless of the product, but specific fields are shown in the form relating to that particular product, e.g.
public class RequestController : Controller
{
// This bit works fine and displays the appropriate form in the view
public ActionResult MyProduct(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
return new HttpNotFoundResult();
}
ProductRequest qd = new ProductRequest();
switch (id)
{
case "Beer":
qd.RequestType = Models.RequestType.Beer;
break;
case "Coffee":
qd.RequestType = Models.RequestType.Coffee;
break;
case "Soda":
qd.RequestType = Models.RequestType.Soda;
break;
}
return View("Index", qd);
}
// Need to get all forms rendered by the above to post here...
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MyProduct(ProductRequest qd)
{
if (!ModelState.IsValid)
{
return View(qd);
}
else
{
// To do ...
}
}
}
When the form is rendered in the view, using...
#using (Html.BeginForm())
...the rendered HTML shows a different form target URL for each product type, e.g:
<form action="/Request/MyProduct/Beer" method="post">
Can I make the form action property use the same controller/method regardless of the product type? e.g.
<form action="/Request/MyProduct" method="post">
I assume there are different ways to achieve this given the flexibility MVC seems to offer, but I'm looking for best practice please as a learning experience.
The Html.BeginForm() call accepts various parameters, which can declare the controller/method you wish the form to use.
#using (Html.BeginForm("MyProduct", "Request", FormMethod.Post, new { enctype = "multipart/form-data"}))
Should produce:
<form action="/Request/MyProduct" enctype="multipart/form-data" method="post">
What added value gives you the Html.BeginForm helper over writting the html yourself, apart from preventing you to easily control your action url?
Personally, I have banned most Html helpers usages, there are just getting in my way instead of helping me.
Write your html code yourself, that is frequently even more concise than using html helpers (as illustrated in this answer).
<form action"#Url.Action("MyProduct", "Request")" method="post">
#* Add [ValidateAntiForgeryToken, HttpPost] attributes to target action *#
#Html.AntiForgeryToken()
...
</form>
(And even better, use UrlHeper extensions to define and centralize your URIs, instead of calling Url.Action inside your views or controllers.)
For completeness, I should mention that most input fields html helper extensions (those suffixed with For) have the added value of exploiting data-annotations of your view-models. But I consider those annotations as bad practice, because they handle subjects which are not the view-model responsibility in strict MVC pattern. (Display labels? Should be controlled by the view. Validation rules? Should be controlled by the controller. Action argument and/or parameter model? No reason to be bound to the view-model names.)

ASP.Net MVC Postback and Models

This is mostly a follow-up to a comment in this issu, but I don't have enough reputation to comment ...
ASP.Net MVC Postback a label value to your controller
Let's say I have a simple model:
public class SimpleClass
{
public String Label { get; set; }
public String FirstName { get; set; }
}
Label is changed based on user/client so it can't be a DataAttribute. If when posted back processing problems occur, we need to redraw the entire page. This is the crux of the problem of the previous post. The accepted solution is to do this:
#Html.DisplayTextFor(model => model.Label)
#Html.HiddenFor(model => model.Label)
#Html.EditorFor(model => model.FirstName)
That makes sense in that it works. But our models are much more complicated and extensive. This method will result in a ton of hidden fields which seems like a very dirty solution.
This brings me to JP's comment:
ASP.Net MVC Postback a label value to your controller
The solution there is to reload the model. But it's not just a reload, it's also a merge since you want to preserve any client-side data changes.
default: SimpleClass { Label="TheLabel", FirstName="Rob"}
postedback: SimpleClass { Label="", FirstName="Steve" }
we want: SimpleClass { Label="TheLabel", "FirstName="Steve" }
My question is does MVC have a good way to know what fields were postedback so it merges correctly? We would need to only merge postedback fields not blank properties.
Or is it better to just ajaxify the entire postback and not do a form submit? This avoids all model reload issues on submit.
Update
To give Pablo credit I accepted his solution. To see my simple example of his solution, check Robert Harvey's comment in the Answers below:
ASP.Net MVC Postback and Models
The main problem here is in trying to fit WebForms' PostBack concepts into MVC. There is no such thing as a stateful postback where things just automatically retain their state.
You only have ViewModels that are bound to the view, and ViewModels that are posted by the view to the Controller. They don't even necessarily need to be of the same Type. Meaning, the controller should only receive the data that the user indeed can change, not large objects with many properties that were part of the initial ViewModel but are read-only.
Labels commonly represent read-only texts and they are not editable form elements. Which is why you have to use hidden fields for that.
And yes, sometimes that implies that you have to reload the original data in the controller, and sync up with new data that you posted, which isn't necessarily a bad thing. If you bind read-only data to a view, which the user can't manually edit, you shouldn't really trust that data coming back in a post afterwards. Just because your html might try to make it read-only doesn't mean I can't manipulate the post and ultimately change your "read-only" data without you knowing.
I just read the second question you mentioned, and from the looks of it, his main problem was that he was trying to reuse the same ViewModel again, so all the data was missing and the model wasn't valid. The solution to that is indeed quite simple, ONLY post what you need, as a new ViewModel type, and have the controller take care of the rest.
[Moved from OP]
I think this is what Pablo is suggesting for those who are wondering. It seems to be a good pattern to resolve this problem.
Models:
public class SimpleClass : SimpleClassPostBack
{
public String Label { get; set; }
public SimpleClass()
{
// simulate default loading
Label = "My Label";
FirstName = "Rob";
}
}
// contains only editable by the user fields
public class SimpleClassPostBack
{
public String FirstName { get; set; }
}
Controller Actions:
[HttpGet]
public ActionResult SimpleClassExample3()
{
SimpleClass simpleClass = new SimpleClass();
return View(simpleClass);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult SimpleClassExample3(SimpleClassPostBack postBackSimpleClass)
{
Boolean errorOccurred = true;
if (!errorOccurred)
{
// do whatever success action is necessary
}
// redraw the page, an error occurred
// reload the original model
SimpleClass simpleClass = new SimpleClass();
// move the posted back data into the model
// can use fancy reflection to automate this
simpleClass.FirstName = postBackSimpleClass.FirstName;
// bind the view
return View(simpleClass);
}
View:
#model SimpleClass
#{
ViewBag.Title = "Simple Class Example3";
}
<h2>Simple Class Example3</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<label for="FirstName">#Html.DisplayFor(m => m.Label)</label>
#Html.EditorFor(m => m.FirstName)
<br/>
<button>Submit</button>
}
You should only send data from the client to the server that the server can't "figure out" on its own. If the server knows what the labels were when the user first navigated to that view then if the user cannot modify them, the server will be able to know what the labels are when reloading the view.
Use hidden fields to identify the database objects. So your SimpleClass should probably have some sort of Id which you will use in the hidden input. Use the EditorFor for FirstName. Now when the form is posted, use the sent Id to find the correct SimpleClass from the database and modify its FirstName property with the value posted. The Label property will be null which is ok since you don't need to save it. Now if there's a problem in the post and you want to send the same view back like it was, you need to repopulate the Label the same way you did when the user arrived to the view for the first time. The values of Id and FirstName properties will be automatically sent back to the view with the model state.
In summary:
Only post data that is needed to identify something and what the user
can edit in that view.
Don't trust the client to send you anything valid. The user can change the values of the hidden field labels to anything.

MVC Model Binding - Maintaining values during a partial edit

I have a model class that goes like
public class Mod
{
public string StaticProp1 {get; set;}
public string StaticProp2 {get; set;}
public string EditableProp1 {get; set;}
}
I want a view in which I can edit EditableProp1 but where StaticProp1, StaticProp2 are displayed but not editable.
I created a strongly-typed view :
#model Mod
#using (Html.BeginForm())
{
<p>#Model.StaticProp1</p>
<p>#Model.StaticProp2</p>
#Html.TextBoxFor(m => m.EditableProp1)
<input type="submit" value="Save" />
}
In my Controller, when I deal with the action I find the EditableProp1 fine.
[HttpPost]
public ActionResult Edit(Mod model, FormCollection collection)
{
string editableProp = model.EditableProp1; //This works fine
string staticProp1 = model.StaticProp1; //Missing
return View(model);
}
This causes a problem if I post back for some reason as staticProp1 will now be null and the view won't be able to display it.
I know that I can add
#Html.HiddenFor(m => m.StaticProp1)
#Html.HiddenFor(m => m.StaticProp2)
to my view and that it will work fine, but I am wondering if there is another better way.
My values are already on the form (<p>#Model.StaticProp1</p>). Is there a way to bind the model to un-editable tags like that? Is there an HTML helper that does something like this?
By the way, if it isn't obvious, I am just starting out with MVC so if I am completely missing the point please let me know!
Every property of a model you want to persist has to be in the form (in an editor or hidden field). You can use, as you propose, Html.HiddenFor() for this. If you want to avoid overloading your view with hidden fields, you could store only the id of an entity in the hidden field and fetch the rest of the data based on the id in the Post action. Or use Html.TextBoxFor() with a readonly attribute, see this question for more information about it (I like the approach in the second answer as well).
I think the question relates more to model binding and how it works. If you don't want to use hidden field here (which I think fits your scenario), you can custom Model Bind by inheriting a class from:
DefaultModelBinder

Categories

Resources