Im trying to do a little render framework, since I need some more control over the render process. Fx. if a property need to be rendered in a tab.
So I set out to, render a TextBox, but it does not validate with server side or client side validation (the MVC unobtrusive validation)
I have taken my framework out, and recreated a little eksampel
public class Foo
{
public virtual int Id { get; set; }
[System.ComponentModel.DataAnnotations.Required]
public virtual string Name { get; set; }
public virtual DateTime StartTime { get; set; }
}
My extension method:
public static MvcHtmlString DummyForm(this HtmlHelper html)
{
StringBuilder sb = new StringBuilder();
Type oftype = typeof(Foo);
string[] propertyNameToRender = oftype.GetProperties().Select(o => o.Name).ToArray();
foreach (string s in propertyNameToRender)
{
MvcHtmlString htmlstring = System.Web.Mvc.Html.InputExtensions.TextBox(html, s);
sb.AppendLine(htmlstring.ToHtmlString());
}
return MvcHtmlString.Create(sb.ToString());
}
And on the Edit.cshtml
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true);
#Html.DummyForm()
}
If I look at the rendered html output, its the same (without the validation attri)
Can anyone tell me, why the validation attri, is not rendered.
Im using the mvc's own render controls, HtmlHelper is passed from the view, with all ModelMetadata and ModelState.
Unobtrusive validation data-val-* attributes are rendered when FormContext is initialized. Html.BeginForm does that, so
#using (Html.BeginForm())
{
#Html.DummyForm()
}
Should render inputs with validation attributes.
There is one thing that seems odd is that you are calling System.Web.Mvc.Html.InputExtensions.TextBox method yourself. This method is internally called by Html.TextBox and other strongly typed extensions. plz try changing
MvcHtmlString htmlstring = System.Web.Mvc.Html.InputExtensions.TextBox(html, s);
to
MvcHtmlString htmlstring = html.TextBox(s);
I have try to create a new ASP.net MVC site, and added the code from my RenderProject, and it works fine. The conclusion is my asp.net MVC project messed up. I dont why. :S
Related
I have a ViewModel that has a complex object as one of its members. The complex object has 4 properties (all strings). I'm trying to create a re-usable partial view where I can pass in the complex object and have it generate the html with html helpers for its properties. That's all working great. However, when I submit the form, the model binder isn't mapping the values back to the ViewModel's member so I don't get anything back on the server side. How can I read the values a user types into the html helpers for the complex object.
ViewModel
public class MyViewModel
{
public string SomeProperty { get; set; }
public MyComplexModel ComplexModel { get; set; }
}
MyComplexModel
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
....
}
Controller
public class MyController : Controller
{
public ActionResult Index()
{
MyViewModel model = new MyViewModel();
model.ComplexModel = new MyComplexModel();
model.ComplexModel.id = 15;
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// model here never has my nested model populated in the partial view
return View(model);
}
}
View
#using(Html.BeginForm("Index", "MyController", FormMethod.Post))
{
....
#Html.Partial("MyPartialView", Model.ComplexModel)
}
Partial View
#model my.path.to.namespace.MyComplexModel
#Html.TextBoxFor(m => m.Name)
...
how can I bind this data on form submission so that the parent model contains the data entered on the web form from the partial view?
thanks
EDIT: I've figured out that I need to prepend "ComplexModel." to all of my control's names in the partial view (textboxes) so that it maps to the nested object, but I can't pass the ViewModel type to the partial view to get that extra layer because it needs to be generic to accept several ViewModel types. I could just rewrite the name attribute with javascript, but that seems overly ghetto to me. How else can I do this?
EDIT 2: I can statically set the name attribute with new { Name="ComplexModel.Name" } so I think I'm in business unless someone has a better method?
You can pass the prefix to the partial using
#Html.Partial("MyPartialView", Model.ComplexModel,
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ComplexModel" }})
which will perpend the prefix to you controls name attribute so that <input name="Name" ../> will become <input name="ComplexModel.Name" ../> and correctly bind to typeof MyViewModel on post back
Edit
To make it a little easier, you can encapsulate this in a html helper
public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string partialViewName)
{
string name = ExpressionHelper.GetExpressionText(expression);
object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
var viewData = new ViewDataDictionary(helper.ViewData)
{
TemplateInfo = new System.Web.Mvc.TemplateInfo
{
HtmlFieldPrefix = string.IsNullOrEmpty(helper.ViewData.TemplateInfo.HtmlFieldPrefix) ?
name : $"{helper.ViewData.TemplateInfo.HtmlFieldPrefix}.{name}"
}
};
return helper.Partial(partialViewName, model, viewData);
}
and use it as
#Html.PartialFor(m => m.ComplexModel, "MyPartialView")
If you use tag helpers, the partial tag helper accepts a for attribute, which does what you expect.
<partial name="MyPartialView" for="ComplexModel" />
Using the for attribute, rather than the typical model attribute, will cause all of the form fields within the partial to be named with the ComplexModel. prefix.
You can try passing the ViewModel to the partial.
#model my.path.to.namespace.MyViewModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
Edit
You can create a base model and push the complex model in there and pass the based model to the partial.
public class MyViewModel :BaseModel
{
public string SomeProperty { get; set; }
}
public class MyViewModel2 :BaseModel
{
public string SomeProperty2 { get; set; }
}
public class BaseModel
{
public MyComplexModel ComplexModel { get; set; }
}
public class MyComplexModel
{
public int id { get; set; }
public string Name { get; set; }
...
}
Then your partial will be like below :
#model my.path.to.namespace.BaseModel
#Html.TextBoxFor(m => m.ComplexModel.Name)
If this is not an acceptable solution, you may have to think in terms of overriding the model binder. You can read about that here.
I came across the same situation and with the help of such informative posts changed my partial code to have prefix on generated in input elements generated by partial view
I have used Html.partial helper giving partialview name and object of ModelType and an instance of ViewDataDictionary object with Html Field Prefix to constructor of Html.partial.
This results in GET request of "xyz url" of "Main view" and rendering partial view inside it with input elements generated with prefix e.g. earlier Name="Title" now becomes Name="MySubType.Title" in respective HTML element and same for rest of the form input elements.
The problem occurred when POST request is made to "xyz url", expecting the Form which is filled in gets saved in to my database. But the MVC Modelbinder didn't bind my POSTed model data with form values filled in and also ModelState is also lost. The model in viewdata was also coming to null.
Finally I tried to update model data in Posted form using TryUppdateModel method which takes model instance and html prefix which was passed earlier to partial view,and can see now model is bound with values and model state is also present.
Please let me know if this approach is fine or bit diversified!
Installed EA for trying to have a parameter be required if there is nothing in the database, IE. this is the first time someone is creating something.
However, the RequiredIf never fires for client side validation, even though when the model gets into the partial view, the BindingExists bool is set to false and the Xml value is still null.
Model:
public class AddTestStepXmlParameterModel
{
public ParameterTypeEnum ParameterType { get; set; }
public string ParameterName { get; set; }
public string Description { get; set; }
[RequiredIf("BindingExists == false", ErrorMessage = "An XML File is required: Please Try again")]
[FileExtensions(Extensions = "xml", ErrorMessage = "Specify an XML file.")]
public HttpPostedFileBase XmlValue { get; set; }
public bool BindingExists { get; set; }
}
Global.asax:
protected void Application_Start()
{
ModelValidatorProviders.Providers.Remove(ModelValidatorProviders.Providers.FirstOrDefault(x => x is DataAnnotationsModelValidatorProvider));
ModelValidatorProviders.Providers.Add(new ExpressiveAnnotationsModelValidatorProvider());
}
Scripts in View:
<script src="~/Scripts/jquery-3.1.0.js"></script>
<script src="~/Scripts/jquery.validate.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="~/Scripts/expressive.annotations.validate.js"></script>
Partial View Call:
#Html.Partial("AddParameters", Model.AddTestStepModel.AddTestStepParametersModel)
Partial View:
#Html.HiddenFor(m => m.AddTestStepXmlParameterModels[k].BindingExists, new {#Value = Model.AddTestStepXmlParameterModels[k].BindingExists})
#Html.TextBoxFor(m => m.AddTestStepXmlParameterModels[k].XmlValue, new {type = "file", #class = "btn btn-default btn-file", style = "color:transparent", onchange = "this.style.color = 'black'"})
#Html.ValidationMessageFor(m => m.AddTestStepXmlParameterModels[k].XmlValue)
When using just a normal "Required", the Xml Value client side fires off fine, however using the RequiredIf fails to do any validation. I've followed the isntallation steps with the Global.asax
Your HTML is not being generated as expected. Due to that, serialized form isn't properly understood by the model binder and cannot be correctly deserialized.
Instead of partial view use the editor template:
move the AddParameters.cshtml template under Views...\EditorTemplates\ directory,
change #Html.Partial(... invocation into #Html.EditorFor(model => model.AddTestStepModel.AddTestStepParametersModel, "AddParameters").
When you compare the output HTML for these two invocations you'll see
short input fields names for the partial view: AddTestStepXmlParameterModels[0].XmlValue,
in contrast to editor template rendering full names, used by the binder to map respective fields: AddTestStepModel.AddTestStepParametersModel.AddTestStepXmlParameterModels[0].XmlValue.
I am creating a MVC-Project. Using MVC 4 and Razor. After building some pages I was wondering: what is the difference between
MvcHtmlString.Create()
and
Html.Raw()
Would be nice if you could help me here to understand that.
Thanks in advance!
This is an excellent opportunity to look at the source code that's available to us for ASP.NET (https://github.com/aspnet/AspNetWebStack/).
Looking at HtmlHelper.cs, this is the code for Html.Raw():
public IHtmlString Raw(string value)
{
return new HtmlString(value);
}
public IHtmlString Raw(object value)
{
return new HtmlString(value == null ? null : value.ToString());
}
And this is the code for the MvcHtmlString class:
namespace System.Web.Mvc
{
public sealed class MvcHtmlString : HtmlString
{
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "MvcHtmlString is immutable")]
public static readonly MvcHtmlString Empty = Create(String.Empty);
private readonly string _value;
public MvcHtmlString(string value)
: base(value ?? String.Empty)
{
_value = value ?? String.Empty;
}
public static MvcHtmlString Create(string value)
{
return new MvcHtmlString(value);
}
public static bool IsNullOrEmpty(MvcHtmlString value)
{
return (value == null || value._value.Length == 0);
}
}
}
The most significant difference is that Html.Raw() accepts any object, while MvcHtmlString.Create() only accepts strings.
Also, Html.Raw() returns an interface, while the Create method returns an MvcHtmlString object.
Lastly, the Create deals with null differently.
There is no practical difference.
The MvcHtmlString.Create creates an instance of MvcHtmlString, while the Html.Raw method creates an instance of HtmlString, but MvcHtmlString just inherits from HtmlString, so they work the same.
The other answers focus more on the technical differences, if there are any. I think however there is another aspect: They serve different use cases / are used in different situations.
Html.Raw(...) is a method of IHtmlHelper. These are intented for use in razor views. It can be used to render raw HTML strings 'as is', without them getting encoded.
Since rendering user generated HTML content can be a security risk, it is very important to know when a string can contain HTML code, and for it to be sanitized. One of the main sources of security problems with old languages like ASP and PHP is rendering strings un-encoded per default, so you can see why, per default, ASP.NET MVC renders strings encoded. You want the (few) cases where your program renders a raw HTML string to be 'opt-in' and clear to see.
To better indicate these cases, it is good practice to store the HTML strings in a dedicated data type, like HtmlString. These objects will be rendered un-encoded, so you don't need Html.Raw. For this you can use MvcHtmlString.Create(...), or, more simply, new HtmlString(...), even if you don't have access to an IHtmlHelper (for example in a view model).
To illustrate this, consider this example of a view model for an ASP.NET MVC view with a title that does not contain HTML, and a content that does:
class MyViewModel
{
public string Title { get; set; }
public HtmlString SomeHtmlContent { get; set; }
}
This can be rendered on the page like this - notice that you don't need Html.Raw to render the HTML content:
<div>
<h1>#Model.Title</h1>
<div>
#Model.SomeHtmlContent
</div>
<div>
We are currently dealing with some XSS issues on one of our ASP.NET MVC projects. I found two issues - the first one has to do with our request validation pattern. The attacker could now use this security hole to drop some bad content in our database.
The second issue is how we display this content and we use the Html.DisplayTextFor method and it seems to be "broken".
Just create a new MVC 3 WebApp, put this in the HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "<SCRIPT/XSS SRC=\"htpp://ha.ckers.org/css.js\">";
User foo = new User();
foo.Name = "<SCRIPT/XSS SRC=\"htpp://ha.ckers.org/css.js\">";
return View(bla);
}
public ActionResult About()
{
return View();
}
}
public class User
{
public string Name { get; set; }
}
The View:
#Html.TextBoxFor(m => m.Name) <br/> ||| <-- will be encoded
#Html.Encode(ViewBag.Message)<br/> ||| <-- will be double encoded
#Model.Name <br/> ||| <-- will be encoded
#Html.DisplayTextFor(m => m.Name) <-- no encoding
<br/> |||
Output of the DisplayTextFor will be the whole string <script xss="" src="htpp://ha.ckers.org/css.js">
Question is: Bug, feature or am I using it wrong?
Html.DisplayTextFor is really for interacting with the [DisplayFormat] attribute (see MSDN).
So if you're using it with unsafe values, you have to be aware of this and use [DisplayFormat(HtmlEncode = true)] on your property.
Edit: Looks like the HtmlEncode property isn't actually enforced by DataAnnotationsModelMetadataProvider (and DisplayTextFor).
Coming from the asp.net background, I really appreciated the concept of 'validationGroup' when adding validation to a page. I've been searching for a corresponding concept within mvc.net and haven't had much luck.
Is this concept available in mvc.net? If not, what alternatives do I have?
Unfortunately no, it doesn't come with anything like that.
I blogged about a workaround a wee while back.
ASP.NET MVC - Validation Summary with 2 Forms & 1 View
The jist of the blog post:
namespace System.Web.Mvc
{
public static class HtmlExtensions
{
public static string ActionValidationSummary(this HtmlHelper html, string action)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
return html.ValidationSummary();
return string.Empty;
}
}
}
And
<h2>Register</h2>
<%= Html.ActionValidationSummary("Register") %>
<form method="post" id="register-form" action="<%= Html.AttributeEncode(Url.Action("Register")) %>">
... blah ...
</form>
<h2>User Login</h2>
<%= Html.ActionValidationSummary("LogIn") %>
<form method="post" id="login-form" action="<%= Html.AttributeEncode(Url.Action("LogIn")) %>">
... blah ...
</form>
HTHs,
Charles
Expanding on Charlino's answer, and including HtmlAttributes and other ValidationSummary properties:
public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, string action, bool excludePropertyErrors, string message, object htmlAttributes = null)
{
var currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
{
return html.ValidationSummary(excludePropertyErrors, message, htmlAttributes);
}
return new MvcHtmlString(string.Empty);
}
Charles's method was the only approach I could find that actually worked for my purposes!
(I.e. two forms on one MVC page -> without doing forms inside partials and ajax loads for the partials. This was no good for me, as I wanted to return differing result sets to be rendered outside the form div, depending on which form was submitted)
I would advise a slight modification to the Html Extension though, because you still want a validation summary to be rendered for the non-matched validation summary so that client side validation works:
namespace System.Web.Mvc
{
public static class HtmlExtensions
{
public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, string action)
{
string currentAction = html.ViewContext.RouteData.Values["action"].ToString();
if (currentAction.ToLower() == action.ToLower())
return html.ValidationSummary();
return new MvcHtmlString("<div class=\"validation-summary-valid\" data-valmsg-summary=\"true\"><ul><li style=\"display:none\"></li></ul></div>");
}
}
}