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.
Related
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.
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 am new to ASP.NET MVC 5. I like to learn the correct way to build a two way binding between the view and the view model. And take advantage of the client side validation script.
Here is what I have done.
I loading jQuery Library v 1.10.1
I loading Jquery-Validation
I loaded jQuery.Unobtrusive.Validation
I created a ViewModel like so
public class RequestFormViewModel
{
[Required]
[Display(Name = "Day Of")]
public DateTime LocalFrom { get; set; }
[Required]
[Display(Name = "Does not matter since this will be hidden and I use javascript to populate the value here when before the for is submitted")]
public DateTime From { get; set; }
public RequestFormViewModel()
{
}
public RequestFormViewModel(DateTime localFrom, DateTime from)
{
this.LocalFrom = localFrom;
this.From = from;
}
}
And this is how I created my view. Note that I pass a presenter or a business layer (i.e. DefaultViewPresenter) to my view and not the ViewModel. The class DefaultViewPresenter has a property nammed Request. (Below I will show how my presenter looks like)
#model Proj.Presenters.DefaultViewPresenter
#using (Html.BeginForm("Index", "Track", FormMethod.Post, new { #class="form-inline", Id = "TrackActionForm" }))
{
#Html.AntiForgeryToken()
#Html.HiddenFor(m => m.Request.LocalFrom, new { Id = "TrackFrom", Name = "From" })
<div class="input-group">
#Html.TextBoxFor(m => m.Request.LocalFrom, new { Value = Model.Request.LocalFrom.ToString("MM/dd/yyyy"), #class = "form-control small", Id = "TrackLocalFrom", Name = "LocalFrom" })
#Html.ValidationMessage("LocalFrom")
<span class="input-group-btn">
<button class="btn btn-info" type="button" id="TrackSubmit">View</button>
</span>
</div>
}
Here is how my presenter looks like
public class DefaultViewPresenter
{
public RequestFormViewModel Request { get; set; }
.... // some other propertied that I need for the view that are not related to my form
....
....
}
Problem
When I submit the form, the form does not get submitted! I don't I get any errors. Its like the submit button has return; function on the click event.
How can I get the script to validate correctly and when the form is valid processed the post request?
What am I missing here? How can I correct this problem?
After running my app this is the HTML markup that is razor is generating
<form novalidate="novalidate" id="TrackActionForm" action="/Track" class="form-inline" method="post">
<input name="__RequestVerificationToken" value="thmJX-Mlj5WjM3e7WMbgtb8KiEf4vuUKGzon4zO18fHDDY3cWpm2M1Lks8HbZDxX2qz7UxpRsoYvz2njNwYS_D8zclTvu9pdsJlSO0ckNLQ1" type="hidden">
<input id="TrackFrom" name="From" data-val="true" data-val-date="The field Day Of must be a date." data-val-required="The Day Of field is required." value="9/14/2016 12:00:00 AM" type="hidden">
<div class="input-group">
<input id="TrackLocalFrom" name="LocalFrom" value="09/14/2016" class="form-control small" type="text">
<span class="field-validation-valid" data-valmsg-for="LocalFrom" data-valmsg-replace="true"></span>
<span class="input-group-btn">
<button class="btn btn-info" type="button" id="TrackSubmit">View</button>
</span>
</div>
</form>
You have a few issues with your implementation.
All the HtmlHelper methods that generate form controls generate the correct name and value attributes necessary for 2 way model binding. You are overriding those values so that the controls now have no relationship to your model.
You also have a hidden input for the Request.LocalFrom property before the textbox, so that when you submit, only the value of the hidden input (the original value of the property) will be bound and the edited value in the textbox will be ignored. In addition, because of that hidden input, the data-val-* attributes generated for client side validation have been applied to the hidden input, not the textbox.
Its not clear why you need 2 view models, and ideally your DefaultViewPresenter view model should contain properties for LocalFrom and From, however with your current models, your view needs to be
#model Proj.Presenters.DefaultViewPresenter
#using (Html.BeginForm("Index", "Track", FormMethod.Post, new { #class="form-inline", Id = "TrackActionForm" }))
{
#Html.AntiForgeryToken()
<div class="input-group">
#Html.TextBoxFor(m => m.Request.LocalFrom, "{0:MM/dd/yyyy}", new { #class = "form-control small" })
#Html.ValidationMessageFor(m => m.Request.LocalFrom)
<span class="input-group-btn">
<button class="btn btn-info" type="submit" id="TrackSubmit">View</button> // change to a submit button
</span>
</div>
}
Note the 2nd parameter in TextBoxFor() is the format string, but that can be omitted if you use the [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] applies to the property. Note also the HtmlHelper methods generate an id attribute based on the property name and there should generally be no reason to overwrite it.
As a side note, it is recommended that in your view models, you make value type properties nullable with the [Required] attribute to protect against under-posting attacks (where a malicious user posts backs and omits a name/value pair for the property, in which case it will be initialized to its default value (DateTime.MinValue).
[Required(ErrorMessage = "Please enter ...")]
[Display(Name = "Day Of")]
public DateTime? LocalFrom { get; set; }
By default JQuery Validation ignores hidden fields for client side validation. Try the following in a script block in your view or _layout.cshtml:
$(document).ready(function(){
$.validator.setDefaults({ ignore: [] });
});
Hope this helps!
I'm not applying any [Required] attribute validation on the integer property. But every time I post the form it fired Validation. Initially it was using HTML 5 data attribute on client side. I've set ClientValidationEnabled to false in Web.config. After that it is firing Required attribute validation.
I've created a new project but same situation. Tried to change .Net framework from 4.6 to 4.5 but no success. Also tried VS2015 and VS 2013
Client side razor code
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
#Html.LabelFor(model => model.Weight)
#Html.TextBoxFor(model => model.Weight, new { #class = "form-control", #placeholder = "Weight", Value = "" })
#Html.ValidationMessageFor(model => model.Weight, "", new { #class = "text-danger" })
<input type="submit" class="btn btn-default" value="Save" />
}
Model:
public class RegistrationModel
{
public int Weight { get; set; }
}
Action Method
public ActionResult Index()
{
var model = new RegistrationModel();
return View(model);
}
[HttpPost]
public ActionResult Index(RegistrationModel model)
{
if (ModelState.IsValid)
{
}
return View();
}
Thanks
Your model property is typeof int. An int must always have a value (its not nullable) so irrespective of disabling client side validation, as soon as you hit the controller, ModelState will be invalid if you submit a null (empty string) value for Weight.
If you want to allow nulls, then you must make the property nullable
public int? Weight { get; set; }
Side note: You should never attempt to set the value attribute when you are using the html helpers to bind to your model properties.
This is default behavior of integer type property because int has to
have a value even zero.So, you have to make it of type nullable int in the model.
this might help you,
<form method="post" action="/foo" novalidate>...</form>
or
<form>
<input type="email"/> <!-- Will be validated -->
<input type="string" formnovalidate/> <!-- Will not be validated -->
</form>
In ASP.NET MVC I have a view that's a form, and I want to be able to save the form and then it returns back to the same page showing the data you entered after saving the data to the database. I'm sure I'm just doing something stupid (this is pretty new to me), but there are some properties that I want to persist and I am setting them on the view model before I return, and I have #Html.HiddenFor inside my view's form. My confusion is that of these items are retained, and some aren't. So I have the following inside my FormController (methods and names have been simplified for brevity):
public ActionResult Index(int? p, int? c)
{
FormViewModel model = new FormViewModel();
model.p = p;
model.c = c;
model.dateStarted = DateTime.Now;
return View(model);
}
[HttpPost]
public ActionResult Index(FormViewModel m)
{
Form form;
bool shouldUpdate = false;
if (m.formID != null) // m.formID is always null, but m.p, c, dateStarted aren't
{
shouldUpdate = true;
form = getFormnWithId((int)m.formID); //gets from database
}
else
{
form = new Form(m);
}
if (shouldUpdate)
{
editForm(form); //edit existing entry
}
else {
addForm(form); //add to database
}
m.formID = form.Id; // formn.Id is valid because the form has been updated with its Id after being added to the database
m.p = form.p;
m.c = form.c;
return View(m);
}
Inside my view (cshtml) file I have #Html.HiddenFor(model=>model.formID) as well as for other properties I want to persist but aren't being set in the form directly.
The formID however, is not persisting, while the other items (represented by c and p and dateStarted) are fine. If I remove the HiddenFor for those other fields, then they don't work. I click save each time, and formID is null in the post, but it's definitely set after the form has been added to the database and the value of the formID is definitely getting sent to the view. I just don't understand why it comes back null but the other properties don't.
Here's what the model looks like:
public class FormViewModel
{
public Nullable<int> formID {get; set;}
public Nullable<int> c { get; set; }
public Nullable<int> p { get; set; }
public System.DateTime dateStarted { get; set; }
//+ other form properties
}
View:
...
<label for="submit-form" class="btn btn-default">Save</label>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal col-md-12">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<!-- various form fields -->
</div>
#Html.HiddenFor(model => model.dateStarted)
#Html.DisplayFor(model => model.dateStarted)<br /> <!-- just to see it while testing-->
#Html.HiddenFor(model => model.c)
#Html.DisplayFor(model => model.c)<br />
#Html.HiddenFor(model => model.p)
#Html.DisplayFor(model => model.p)<br />
#Html.HiddenFor(model => model.formID)
#Html.DisplayFor(model => model.formID)
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" name="Command" class="btn btn-default hidden" id="submit-form" />
</div>
</div>
</div>
}
Now that I see you are setting the Form.Id in the POST request, your issue is that you are not following the PRG (Post, Redirect, Get) pattern. You are returning the same view from your POST method without any type of redirect. As a result, the model binder is holding on to the previous value of Form.Id, which was null. The reason that the model binder holds on to previous values is mainly for validation purposes (if the ModelState has an error, you can return the view, the properties remain as the user entered them along with the ModelState errors collection)
To fix this, you either need to redirect to another action or issue ModelState.Clear() in your code before you return the view.
m.formID = form.Id; // form.Id is valid because the form has been
//updated with its Id after being added to the database
m.p = form.p;
m.c = form.c;
ModelState.Clear();
return View(m);