can somebody help me?
I have a model:
public class EditUserVM
{
public string Role {get;set;}
public IEnumerable<SelectListItem> AllRoles { get; set; }
}
I have a controller:
public class AdminController : Controller
{
// GET: Admin/Admin/EditUser/id
[HttpGet]
public ActionResult EditUser(string id)
{
ApplicationUser user = UserManager.FindById(id);
EditUserVM model;
//model initialization
return View(model);
}
// POST: Admin/Admin/EditUser
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> EditUser(EditUserVM model)
{
if (!ModelState.IsValid)
{
return View(model);
}
//code
return View(model);
}
}
And I have a view:
#model EditUserVM
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.Role, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Role", Model.AllRoles, new { #class= "btn btn-light"})
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-success" />
</div>
</div>
}
But when I click on the save button, then in the post controller action I don’t get model.AllRoles I mean, that model.AllRoles == null.
How can I get these values?
When the user submits the form (which then generates the callback to the [HttpPost]-variant of your EditUser method), the browser only submits the selected value of the drop down list, and not the entire list of possible selections. On the server side, an instance of the viewmodel is created and populated with what the browser sent. Since the browser hasn't sent the list of all possible options, that field is empty in your ViewModel.
This behavior makes sense. You're not interested in the list of possibilities (in fact, you already KNOW that list, because you sent it to the browser in the [HttpGet] method). You're only interested in the actual value that the user selected. If the ModelState is not valid, and you use that ViewModel to generate a new View, you need to repopulate AllRoles again.
Related
I am working on a functionality based on ASP.NET MVC 5 to manage message templates which are rendered as html. Having html-markup in the viewmodel causes some problems.
Those message templates are edited via a WYSIWYG-editor.
Here a basic example of the controller:
public class BlackboardController : Controller
{
public ActionResult Template()
{
return View(new RichTextEditorViewModel()
{
Message = "<h1>I'm a headline</h1><p>I'm a regular text...</p>"
});
}
[HttpPost]
public ActionResult Template(RichTextEditorViewModel model)
{
if (!ModelState.IsValid)
return View(model);
return RedirectToAction("Template");
}
}
Basic example of the model:
public class RichTextEditorViewModel
{
[Required]
[Display(Name = "Template name")]
public string TemplateName { get; set; }
[AllowHtml]
[Display(Name = "Message")]
public string Message { get; set; }
}
Part of the view
#using (Html.BeginForm("Template", "Blackboard", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
<div class="form-group">
<label class="col-md-4 control-label">Message </label>
<div class="col-md-8">
<div class="input-group">
#Html.TextAreaFor(m => m.Message, new { rows = "20", style = "resize:none;width:400px;", placeholder = Html.DisplayNameFor(m => m.Message), #class = "form-control input-lg textarea-editor" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save template" class="btn btn-default" />
</div>
</div>
}
Everything works fine when I post a html-markup to the controller action. To make that work, I had to decorate the model property containing the markup with the AllowHtml-attribute.
BUT: If the ModelState is not valid, e.g. TemplateName is null, then I still get that HttpRequestValidationException saying:
"A potentially dangerous Request.Form value was detected from the
client"
I couldn't reproduce that behaviour with that basic example, but it happens in my more complex web application. On some sites I found the information, that an exception gets thrown if anything touches a property of the Request-property of the controller or view. Tried to work on that, but it didn't seem to help. Also, I don't know what components are actually accessing the request or containing a reference to that request.
How can it be, that I won't see this exception if the ModelState is valid. And how can it be, that the HttpRequestValidationException gets thrown when the ModelState is invalid.
The problem I will be describing is very similar to ones I already found (e.g. this post with nearly identical name) but I hope that I can make it into something that is not a duplicate.
I have created a new ASP.NET MVC 5 application in Visual Studio. Then, I defined two model classes:
public class SearchCriterionModel
{
public string Keyword { get; set; }
}
public class SearchResultModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string Surname { get; set; }
}
Then I created the SearchController as follows:
public class SearchController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult DisplaySearchResults()
{
var model = new List<SearchResultModel>
{
new SearchResultModel { Id=1, FirstName="Peter", Surname="Pan" },
new SearchResultModel { Id=2, FirstName="Jane", Surname="Doe" }
};
return PartialView("SearchResults", model);
}
}
as well as views Index.cshtml (strongly typed with SearchCriterionModel as model and template Edit) and SearchResults.cshtml as a partial view with model of type IEnumerable<SearchResultModel> (template List).
This is the Index view:
#model WebApplication1.Models.SearchCriterionModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SearchCriterionModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Keyword, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Keyword, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Keyword, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="button" id="btnDisplaySearchResults" value="Search" onclick="location.href='#Url.Action("DisplaySearchResults", "SearchController")'" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
<div id="searchResults">
</div>
As you can see, I added a div with id="searchResults" below the standard template and edited the button. What I want is to display the partial view SearchResults.cshtml in the div on the bottom, but only after the button is clicked. I have succeeded in showing a partial view there by using #Html.Partial("SearchResults", ViewBag.MyData), but it is rendered when the parent view is loaded for the first time and I set ViewBag.MyData in the Index() method already, which is not what I want.
Summary: On clicking the button, I will obtain some List of SearchResultModel instances (via database access) and then the partial view should be rendered, using this newly obtained data as model. How can I accomplish this? I already seem fail at the first step, that is reacting to the button click with the above code. Right now, I navigate to the URL ~/Search/DisplaySearchResults, but of course there's nothing there and no code-behind method is called.
In traditional ASP.NET I'd just have added a server-side OnClick handler, set the DataSource for a grid and show the grid. But in MVC I already fail with this simple task...
Update: Changing the button to #Html.ActionLink I can finally enter the controller method. But naturally since it returns the partial view, it's displayed as the whole page content. So the question is: How do I tell the partial view to be rendered inside a specific div on the client side?
Change the button to
<button id="search">Search</button>
and add the following script
var url = '#Url.Action("DisplaySearchResults", "Search")';
$('#search').click(function() {
var keyWord = $('#Keyword').val();
$('#searchResults').load(url, { searchText: keyWord });
})
and modify the controller method to accept the search text
public ActionResult DisplaySearchResults(string searchText)
{
var model = // build list based on parameter searchText
return PartialView("SearchResults", model);
}
The jQuery .load method calls your controller method, passing the value of the search text and updates the contents of the <div> with the partial view.
Side note: The use of a <form> tag and #Html.ValidationSummary() and #Html.ValidationMessageFor() are probably not necessary here. Your never returning the Index view so ValidationSummary makes no sense and I assume you want a null search text to return all results, and in any case you do not have any validation attributes for property Keyword so there is nothing to validate.
Edit
Based on OP's comments that SearchCriterionModel will contain multiple properties with validation attributes, then the approach would be to include a submit button and handle the forms .submit() event
<input type="submit" value="Search" />
var url = '#Url.Action("DisplaySearchResults", "Search")';
$('form').submit(function() {
if (!$(this).valid()) {
return false; // prevent the ajax call if validation errors
}
var form = $(this).serialize();
$('#searchResults').load(url, form);
return false; // prevent the default submit action
})
and the controller method would be
public ActionResult DisplaySearchResults(SearchCriterionModel criteria)
{
var model = // build list based on the properties of criteria
return PartialView("SearchResults", model);
}
So here is the controller code.
public IActionResult AddURLTest()
{
return ViewComponent("AddURL");
}
You can load it using JQuery load method.
$(document).ready (function(){
$("#LoadSignIn").click(function(){
$('#UserControl').load("/Home/AddURLTest");
});
});
source code link
I have a model, MyModel, with a list property and an accompanying string used to store the values in the database.
Simplifying a bit to increase readability.
public class MyModel
{
public int ID { get; set; }
public List<int> Numbers { get; set; }
[Column, ScaffoldColumn(false)]
public string NumbersStore { get { /*parses Numbers*/ } set { /*splits the value and sets Numbers accordingly*/ } }
}
I'm using basic CRUD, based off the scaffolding.
In my Create/Edit views, I have manually written a select multiple. Not using helpers was probably a bad idea. I have no problem retrieving these values in the Index, Details, and Delete views, but I cannot figure out how to actually bind this data to my model when creating/editing.
Just from some blind Googling, I've tried:
- Added a list to the MyModelController.Create parameters list
- Attempted to use Request.Form["SelectNameAttribute"]
CSHTML:
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.Numbers, htmlAttributes: new { #class = "control-label col-md-2" })
<select class="select2 col-md-10 form-control" multiple="multiple" id="Numbers" name="Numbers">
<!-- Ultimately enumerated with a loop that goes through the database, probably butchering MVC conventions. Don't think that's relevant to the question. -->
<option value="1">One</option>
<option value="2">Two</option>
<!-- Etc. -->
</select>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
Controller:
public ActionResult Create([Bind(Include = "ID,NumbersStore")] MyModel myModel) // Changing NumbersStore to Numbers does nothing
{
//myModel.NumbersStore = Request.Form["Numbers"].ToString();
if (ModelState.IsValid)
{
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myModel);
}
Just recapping the solution from the comments in the question.
The [Bind(Include = "")] needs to include the the properties from the form being posted.
In this case the NumbersStore needs to be Numbers to match the property in the HTML.
[HttpPost]
public ActionResult Create([Bind(Include = "ID,Numbers")] MyModel myModel)
{
// do something with numbers
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
You can pass values to the controller just my simply assigning a parameter with the same name as the html tag:
You can give your select a name (notice the brackets for a list of items)
<select class="select2 col-md-10 form-control" multiple="multiple" id="Numbers" name="Numbers[]">
<option value="1">One</option>
<option value="2">Two</option>
</select>
Then in controller use parameter int[] Numbers to access the posted values
[HttpPost]
public ActionResult Create([Bind(Include = "ID,NumbersStore")] MyModel myModel, int[] Numbers)
{
// do something with numbers
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
Lloyd's answer got me close.
I look like a fool here. The answer is as simple as naming the select and binding that same name to the controller action -- something I could have sworn I'd done, but I think I bound the store rather than the actual list.
I'm not sure if the name attribute has to be the same as the name of the model's property, but I did it to be safe (and for clarity).
So the correct code is:
MyModel.cs
public class MyModel
{
public int ID { get; set; }
public List<int> Numbers { get; set; } // This is the list we're changing
[Column, ScaffoldColumn(false)]
public string NumbersStore { get { /*parses Numbers*/ } set { /*splits the value and sets Numbers accordingly*/ } }
}
MyModelView.cshtml
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.Numbers, htmlAttributes: new { #class = "control-label col-md-2" })
<select class="select2 col-md-10 form-control" multiple="multiple" id="Numbers" name="Numbers"> // Notice name attribute
<option value="1">One</option>
<option value="2">Two</option>
<!-- Etc. -->
</select>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
}
MyModelController.cs .Create
public ActionResult Create([Bind(Include = "ID,Numbers")] MyModel myModel) // Notice name attribute in Bind
{
if (ModelState.IsValid)
{
db.MyModels.Add(myModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myModel);
}
Note:
- Create does not need an array as a parameter (unless you need to make further changes)
- The name HTML attribute does not need to end with [] -- as described here by Sergey, that's a PHP convention that has no effect on ASP.NET
- As far as I know, the name attribute does need to be the same as the property that is eventually being mutated
I am trying to add a new record to a database that I've created. The database is called QUESTIONNAIRES and it has the columns: QuestionnaireUID, UserUID, QuestionnaireName, DateCreated, Link, and Image.
I want the user to specify the QuestionnaireName and provide an Image and I want to generate myself the QuestionnaireUID, UserUID, DateCreated, and Link. So far, this is my View() that represents this creation process:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
// Hide QuestionnaireUID, UserUID, and Link from user. These fields will be generated instead of assigned by user input.
#Html.HiddenFor(model => model.QuestionnaireUID)
#Html.HiddenFor(model => model.UserUID)
#Html.HiddenFor(model => model.Link)
<div class="form-group"> <!-- Questionnaire name. -->
<h2>Name</h2>
<p> Please provide a name for your decision tree.<p>
<div class="col-md-10">
#Html.EditorFor(model => model.QuestionnaireName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.QuestionnaireName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group"> <!-- Questionnaire image. -->
<h2>Image</h2>
<p> Please provide a background image for your decision tree.</p>
<!-- ADD FILE IMAGES & ENCODE IN BINARY. -->
<div class="col-md-10">
#Html.EditorFor(model => model.Image, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Image, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group btn_next"> <!-- Save and continue button. -->
<input type="submit" value="Save and Continue" class="btn">
</div>
}
The Questionnaire Controller methods that are being used are displayed below as well:
// GET: Questionnaires/Create
public ActionResult Create()
{
ViewBag.UserUID = new SelectList(db.Users, "UserUID", "FirstName");
return View();
}
// POST: Questionnaires/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "QuestionnaireUID, UserUID, QuestionnaireName, DateCreated, Link, Image")] QUESTIONNAIRE questionnaire)
{
if (ModelState.IsValid)
{
db.QUESTIONNAIRES.Add(questionnaire);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(questionnaire);
}
As you can see, I've hidden away the three attributes that I want to generate in my View(). I now don't know where I generate and assign these values. Do I do this in my View() or in the Controller method? And what does this assignment look like?
I'd generate those values in the Controller on HttpGet, and I'd use a ViewModel.
Echoing mituw16's comment, using a ViewModel is a good way of keeping everything logically consistent and separated. There's some pretty good in-depth discussions and explanations of ViewModels elsewhere that are worth reading.
Your ViewModel could, for instance, look something (roughly) like this:
public class QuestionnaireViewModel
{
public Guid QuestionnaireUID { get; set; }
public Guid UserUID { get; set; }
public string QuestionnaireName { get; set; }
public DateTime DateCreated { get; set; }
public string Link { get; set; }
public Image Image { get; set; }
}
And it could be passed to the View like this:
[HttpGet]
public ActionResult Create()
{
var vm = new QuestionnaireViewModel();
vm.QuestionnaireUID = Guid.NewGuid();
vm.UserUID = Guid.NewGuid();
return View(vm);
}
When the form is posted, MVC can automatically interpret the incoming data as a QuestionnaireViewModel:
[HttpPost]
public ActionResult Create(QuestionnaireViewModel vm)
{
if (ModelState.IsValid)
{
// map the viewmodel properties onto the domain model object here
db.SaveChanges();
return RedirectToAction("Index");
}
return View(questionnaire);
}
A couple more points:
In this example, you'll see that it may not even be necessary to include the UID stuff in the ViewModel, as the ViewModel only cares about data gathered from/presented to the user. Further, unless the #Html.HiddenFors have some sort of functional purpose on the view, you might be able to leave them out and generate them on HttpPost.
If you're looking to "create new record with a combination of values being assigned from user input and being generated in ASP.NET MVC 4" (a.k.a. creating a form in MVC), then the more complex your model/viewmodel gets, the more I'd stay away from using ViewBag for these purposes.
I have a MVC project im currently working on and i have a check box in one of my views. When i change the check box it never passes the correct value to my modal?
View CheckboxFor:
#model OBASA.Models.EmployeeModal
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>EmployeeModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Active)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.Active)
#Html.ValidationMessageFor(model => model.Active)
</div>
<p>
<input type="submit" name="Sub" value="Save" />
</p>
</fieldset>
}
Modal:
public class EmployeeModal
{
public bool Active { get; set; }
}
In run time when i check or un check the check box it never passes the value, its always false.
Please can someone tell me how to bind this correctly for MVC.
The code you've posted is correct, though you have not posted your controller code. It needs to look something like this:
public ActionResult Junk()
{
return View();
}
[HttpPost]
public ActionResult Junk(EmployeeModal model)
{
bool bActive = model.Active;
return View(model);
}