In our ASP.Net MVC 3 application we're getting truncated strings on our forms when the string value contains a double-quote.
For example, given a textbox:
#Html.TextBoxFor(m => m.County)
If the user enters the string: 'Hampshire"County', when rendering the value back out to the form, only the string 'Hampsire' is displayed. If I inspect the value in the model, the double-quote is escaped as 'Hampshire\"County'. In Fiddler, the posted value is correct and the value is stored in the database correctly, so it would appear to be related to the Html helper that renders the textbox out to the client.
Can anyone shed some light on this?
I was unabled to reproduce this issue. Just created a new MVC 3 project, created a controller with 2 actions (GET and POST), with a typed view, receiving an object with a string property Text, and displaying it inside a form... It all proceeds as expected. No issues. Everything I type in, is posted, and comes back correctly in the POST.
View
#model MvcApplication1.Models.MyClass
#{
ViewBag.Title = "Home";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Home</h2>
#using (Html.BeginForm())
{
#Html.TextBoxFor(m => m.Text)
}
Controller
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(MyClass data)
{
return View(data);
}
}
Model
public class MyClass
{
public string Text { get; set; }
}
So, the problem must be somewhere else... with something that can mess with the value being rendered (editor templates, javascript)
Try looking at the source of the rendered page to see if the value there is correct, if it is, then it must be javascript, otherwise, it must be something in the server.
As it turns out this was a problem of our own making. It turns out some bright spark (me) about 9 months ago thought it would be a good idea to override the HtmlAttributeEncode() method in a custom HttpEncoder. This fires during output rendering.
The reason I added this was that parts of the application need to render script blocks to the client, and the default encoder was screwing with the JS code in the HTML.
When we removed the overriding method, the problem went away. Lesson learned: don't screw around with the framework unless you absolutely need to.
Once again, thanks all for taking the time to consider this.
Related
With an MVC model that has a list of objects as a property, MVC 5 (and at least as far back as 3) does not throw an error if the value submitted for an integer property cannot be parsed as an integer.
MCVE
The following example code demonstrates the problem:
C#:
using System.Web.Mvc;
namespace ModelBindingTest
{
public class MyModel
{
public int NotNullValue { get; set; }
public int? NullableValue { get; set; }
}
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
ViewBag.Method = "GET";
return View(new MyModel()
{
NotNullValue = 999,
NullableValue = 777,
});
}
[HttpPost]
public ActionResult Index(MyModel model)
{
ViewBag.Method = "POST";
return View(model);
}
}
}
Home.cshtml:
#model ModelBindingTest.MyModel
#{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<body>
<form action="" method="POST">
<label for="NotNullValue">NotNullValue:</label>
<input id="NotNullValue" name="NotNullValue" value="#Model.NotNullValue">
<label for="NullableValue">NullableValue:</label>
<input id="NullableValue" name="NullableValue" value="#Model.NullableValue">
<button type="submit">Save</button>
</form>
<p>HTTP Method: #ViewBag.Method </p>
</body>
</html>
The HTML is mostly a convenience for seeing what happens. It's reproducible by POSTing JSON as well. These are the results I get:
222 for NotNullValue: model.NotNullValue is 222 as expected.
Blank NotNullValue: model.NotNullValue is 0.
Letters for NotNullValue (e.g., hre): model.NotNullValue is 0.
222 for NullableValue: model.NullableValue is 222 as expected.
Blank NullableValue: model.NullableValue is null.
Letters for NullableValue (e.g., hre): model.NullableValue is null.
Please ignore the fact I'm reusing the model object for both GET and POST requests. I'm not doing that in production (though I have seen it done); that is just a simplification for the demo.
Goal
I want MVC to actually throw an error if it cannot parse an input to the type specified on the model object. The behavior as is can convert invalid values to valid ones, resulting in persisting data that the client probably did not intend.
The behavior for letters on an int? property is especially problematic for me since I have a field where I need to allow null as a valid value.
I'd prefer a 400 error, but for the moment, any error is an improvement.
Things I've tried
Changing the model to require the values. I get a "No parameterless constructor defined for this object." error, no matter what values are submitted.
Change the input type to string and validate it myself. Sure. I can do this. I can also just throw away the entire MVC framework and use something else. There is virtually no point in having strongly typed model objects if I go this route.
Change the input to a method parameter. This almost works, somewhat. It causes a 500 response with error message "The parameters dictionary contains a null entry for parameter 'NotNullValue'...". But as the error message suggests, it doesn't work for int?. This also isn't viable in production, as some of my data consists of lists of model objects of unknown length, which produce the described behavior on individual items regardless of whether the list is a model property or method parameter.
What can I do to get MVC to actually treat the parsing error as an error?
ASP.NET Core MVC
I do not know if the behavior differs in Core MVC. If so, switching to Core may be a useful answer for someone else (and I would not downvote it), but it's not feasible for me at the moment. So I'm looking for some other workaround.
I'm still learning the MVC way of doing web development.
I have a partial view that renders information for a single photo (picture, username, f-stop, other info)
I have several pages where I want to display lists of photos. For example, on my homepage, I want to display the most recent photos to be added to the site.
My current approach to doing this is that I have added a GetNewestPhotos() function to my PhotoController that goes to the database to get the most recent photo records, and, for each one, renders the partialview and concatenates it to the result string (using the nasty-looking RenderPartialViewToString found here). Then client side, I request this string via AJAX and populate a div with the result.
I'm almost sure that this is wrong. How should I do it?
In your controller method, return a partial view and inject your compound object into the view
public class CompoundType
{
public List<Photo> Photos { get; set; }
}
public ActionResult GetNewestPhotos()
{
CompoundType model = provider.GetPhotosFromDbAsCompoundObject();
return PartialView("ViewName", model);
}
In your view, specify that your view's model should be your compound object.
#model CompoundType
At that point, simply iterate over the properties and/or collections in your object to render them into html.
#foreach (var photo in Model.Photos)
{
#Html.Raw(photo.Name).
...
}
There are a number of reasons why this is preferable over your current approach.
Razor gives you strong typing. You can see what your types are and you get vastly more useful runtime exceptions, allowing you to troubleshoot issues more easily.
In your current paradigm, you are actually doing work twice. You are creating the partial views, but then you are taking them and splicing them together on the client. This is redundant work.
Maintainability. Other devs expect to see the pattern I've outlined. By being consistent, you'll find more useful information online and be able to solve problems more quickly when you encounter them. In addition, you can more easily hand over your project with less knowledge transfer.
You have a view model for the page that contains a list of the photo view models. This page view model contains a list of viewmodels for the photos.
In the View for the main page call:
#Html.DisplayFor(m => m.PhotosView)
This will render each view model using the default view you defined.
edit
class MainPageController
{
ActionResult Index()
{
var model = new MainPageViewModel
{
Photos = GetListOfPhotoViewModelsOrderedByAge(SomeDataSource),
}
return View(model)
}
class MainPageViewModel
{
// various other properties
IList<PhotoViewModels> Photos {get; set;}
}
class PhotoViewModel
{
// properties to display about the photo (including hte path to the actual image)
}
The Razor views (mainpage)
#model MainPageViewModel
#Html.DisplayFor(m =>m.Photos)
#* other things on the page *#
Photo view (in the shared/display directory)
#model PhotoViewModel
<img url="#Model.PathToImage" />
I haven't tried this and it's mostly from ther top of my head, there may be slight syntax errors.
I think I'm missing some fundamentals on how MVC forms work. I have a search form on my home page that has five or six different fields a user can search on. So I have this POSTing to my results action just fine. The Result action looks like this:
[HttpPost]
public ActionResult Results(SearchModel model)
{
ResultsModel results = new ResultsModel();
results.ResultList = SearchManager.Search(model).ToList();
return View("Results", results);
}
I've simplified the above method for this post, but the idea is the same. So this all works fine. My results page shows up with the list of results and my user is at the following URL:
http://www.site.com/results
So...now I want to do something fairly common. I have two dropdown lists on the results page. "Sort by" and "# of results per page". How do I do that and send the full set of model data back to the controller so I can query with the new parameters? In reality, the SearchModel class has about 60 different fields. Potentially all of that data could be contained in the model. How do you persist that to a page "post back"?
This same question has me a little stumped about how to do paging as well. My paging links would go to a URL like:
http://www.site.com/results/2
But that assumes that we're responding to a GET request (I don't want 60 fields of data in the querystring) and that the model data is passed between GET requests, which I know isn't the case.
As I said, I think I'm missing some fundamentals about working with MVC 3, models and form posts.
Can anyone help point me in the right direction here? I'll be happy to edit/update this post as needed to clarify things.
EDIT: I also wanted to point out, I'd like to avoid storing the view model in a Session variable. This site will eventually end up being load balanced in a web farm and I'm really trying to avoid using Session if possible. However, if it's the only alternative, I'll configure another session state provider, but I'd prefer not to.
You can add your current SearchModel parameters to the route values for your form. Several versions of BeginForm allow you to pass in an object/RouteValuesDictionary.
#Html.BeginForm("Action", "Controller", new { SearchModel = Model }, FormMethod.Post)
This should pass-through your current SearchModel values so you can re-use them to get the next page. You need to have a controller action defined that will accept any current-page form values as well as the SearchModel.
I have not done this with form posts, but from what I have done and from what the docs say, this is where I would start. Of course, this also means that each of your page number "links" on the page will need to be doing posts. That is really inconvenient for users if they want to be able to use the Back button in the browser.
In this context, you can try to define a route that allows the page number to appear as a part of the URL -- "Action/Controller/{page}". However, I am not sure how that will work given that the form is doing a post.
Response to Comment:
Yeah, you can use Route Values to add the SearchModel to each page link, but as I said in the comment above, since the links will do a "get," your users will see the SearchModel serialized as a part of the link.
Either way, using Route Values is your answer to getting back your original SearchModel without using hidden fields, Session, or TempData.
Your SearchModel class needs to contain your search criteria and your results. Something like below. If you use a PagedList for your results then it will contain the current page, total pages, total items, etc. You can limit the amount of information in your page by only writing the search criteria that contain values.
public class SearchModel
{
public string Product { get; set; }
public string Sku { get; set; }
public string Size { get; set; }
public string Manufacturer { get; set; }
// etc...
public PagedList ResultsList { get; set; }
}
[HttpPost]
public ActionResult Results(SearchModel model)
{
model.ResultList = SearchManager.Search(model).ToList();
return View(model);
}
One of the options I'm coming up with here is to implement a distributed caching system that supports acting as a custom session provider (i.e. Memcached or Windows Server AppFabric), thereby allowing me to use TempData (and Session) in a load balanced environment like so:
[HttpPost]
public ActionResult Results(SearchModel model)
{
ResultsModel results = new ResultsModel();
results.ResultList = SearchManager.Search(model).ToList();
TempData["SearchModel"] = model;
return View("Results", results);
}
[HttpGet]
public ActionResult Results(int? page)
{
SearchModel model = (SearchModel)TempData["SearchModel"];
ResultsModel results = new ResultsModel();
results.ResultList = SearchManager.Search(model).ToList();
TempData["SearchModel"] = model;
return View("Results", results);
}
Any thoughts on this approach? Seems like a lot to have to go through just to get search parameters passed between requests. Or maybe I was just spoiled with this all happening behind the scenes with WebForms. :)
This seems to be another interesting option for Webforms spoiled guy ;) Persisting model state in ASP.NET MVC using Serialize HTMLHelper
Some kind of ViewState incarnation. It is part of MVC Futures . Not sure how long it is in Futures project and why it cannot get into main lib.
Hello I have recently began work on a largely JQuery/JQueryUI based ASP .Net website. The idea was to have only one page and to have the rest of the content be dynamic, etc loaded in through dialogs and ajax.
The problem is however when a Create & a Edit form for the same model are open in dialogs at the same time some JQueryUI widgets such as the DatePicker stop working as the forms cause the DOM to have duplicate id's on the fields which are present in both.
So I tried using this code on the controller:
ViewData.TemplateInfo.HtmlFieldPrefix = "Create"; // or Edit etc
This worked to fix the DatePicker problem, but the fields no longer mapped to the model when they were posted back to the controller.
Does anyone know how to fix this?
You could try specifying the same prefix when binding back:
[HttpPost]
public ActionResult Create([Bind(Prefix = "Create")] CreateViewModel model)
{
...
}
For anyone having the same issue, you could also just rename your model to "create" as shown below:
[HttpPost]
public ActionResult Create(CreateViewModel create)
{
...
}
Which I think looks nicer, but it's a bit risky. If another developer, or you, decides to change the parameter name later the form will break which is not ideal.
I figured this out as one of my forms was binding correctly and the other wasn't. One had the prefix as the parameter name and one was just "model".
New to MVC so forgive me if the terminology is a little off.
I'm using ASP.NET MVC3 beta and VS 2010.
I'm not sure if this is an error of concept, of syntax, or what.
Essentally, what I'd like to do is, for _layout.cshtml, I would like to include the jQuery script for any Controller whose ActionResult sets ViewModel.UseJQuery to true.
I'm new to things, so I could be wrong, but it seemed like the best way to do this (what I'm currently attempting) would be:
_layout.cshtml file:
#if(View.UseJQuery)
{
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
</script>
<script type="text/javascript">
$(document).ready(function() {
// This is more like it! SHOULDN'T BE SEEN IF UseJQuery is false!
});
</script>
}
In the Various Controllers
public ActionResult Index()
{
ViewModel.UseJQuery = false;
ViewModel.Title = "Image Gallery";
GalleryModel gm = new GalleryModel();
ViewModel.Testy = gm.TestString;
return View();
}
However, this gives me an error about having to convert a null to a boolean (I assume it's not finding the UseJQuery flag).
So, my question is two-fold:
Is this the correct way to go about this?
If so, where am I going wrong syntactically?
I'm sure this is probably just beginner's pains but I looked around and couldn't find a solution at first (I have an ASP.NET MVC book on order -- promise!)
Thanks in advance for any help!
Edit/Update:
Is this something that might be different between MVC 2 and 3? For example, out of the box, the Index() ActionResult of the HomeController.cs is:
public ActionResult Index()
{
ViewModel.Message = "Welcome to ASP.NET MVC!";
ViewModel.Title = "Home";
return View();
}
EDIT / UPDATE: Problem Found!
D'oh! I realized that the code works when the variable is set, but I've been trying to experiment with the variable not set (which of course causes a null value to be passed instead of a false).
So, the question now is, what logic do I put in _layout.cshtml that will allow me to capture a null and set it to false instead?
I'm thinking something along the lines of:
#if(View.UseJQuery.IsNull()){ #View.UseJQuery = false; }
However, a few issues with this:
is IsNull() the correct function, or is my syntax wrong? (lack of syntaxsupport for Razor in VS 2010 is killing me, haha)
How do I set the UseJQuery variable locally in the layout? I doubt View.UseJQuery will work because that's something the controller sets, right?
At any rate, the error I'm getting trying to set the above value is "Invalid expression term '='", so I believe the ViewModel variable collection may be read-only to the View?
--
Sean
You don't seem to be passing your model to the view. Is _layout.cshtml set up as a strongly-typed view expecting a ViewModel object?
Normally I'd expect to see something similar to this:
public ActionResult Index()
{
GalleryModel gm = new GalleryModel();
var model = new ViewModel {
UseJQuery = false,
Title = "Image Gallery",
Testy = gm.TestString
};
return View(model);
}
Looking at your code, is ViewModel a class or an object with a capital V? Either way, the important bit is passing it to the View() method.
In your view page you'll want to specify that its a strongly types view, expecting a model of type ViewModel.
Normally you'd inherit from System.Web.Mvc.ViewPage<T>
in your controller you define:
ViewModel.UseJQuery
yet in your view you use:
if(View.UseJQuery)