I have some fields in my ViewModel that I only need for the output in my View. But they are always getting send when submitting the form. That's not only annoying, but also dangerous, because I definitly don't want that databinding for these viewmodel fields.
Are there any change to define a OneWay Databinding for some of my viewmodel properties?
Thx in advance
EDIT:
The problem ist, that the Pictures and ValidSizes List is send back to the server when I click to an ActionLink.
http://localhost:52176/?PageSize=30&Index=31&Pictures=System.Collections.Generic.List%601[System.String]&Size=100&ValidSizes=System.Collections.Generic.List%601[System.Web.Mvc.SelectListItem]
public class PicturesViewModel
{
public const int SMALL = 100;
public const int MIDDLE = 150;
public const int BIG = 250;
public int PageSize { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="PicturesViewModel"/> class.
/// </summary>
public PicturesViewModel()
{
Pictures = new List<string>();
Size = SMALL;
Index = 1;
PageSize = 30;
}
/// <summary> Gets or sets the index. </summary>
public int Index { get; set; }
/// <summary>
/// Gets or sets the picture links.
/// </summary>
/// <value>The picture links.</value>
public List<string> Pictures { get; private set; }
/// <summary>
/// Gets or sets the size.
/// </summary>
/// <value>The size.</value>
public int Size { get; set; }
private List<SelectListItem> validSizes = null;
/// <summary>
/// Gets the valid sizes.
/// </summary>
/// <value>The valid sizes.</value>
public IEnumerable<SelectListItem> ValidSizes
{
get {
if (validSizes != null)
return validSizes;
validSizes = new List<SelectListItem>
{
new SelectListItem(){Text = "Small", Value = SMALL.ToString()},
new SelectListItem(){Text = "Middle", Value = MIDDLE.ToString()},
new SelectListItem(){Text = "Big", Value = BIG.ToString()}
};
return validSizes;
}
}
}
EDIT2:
<div id="pager_left">
<%= Html.ActionLink("Prev", "Prev", Model)%>
</div></td>
That's the Action Link that causes the binding.
Use separate ViewModel for form input
Use IEnumerable instead of List
Use [Bind(Exclude="Pictures, ValidSizes")] on the action input parameter
Use private setters
Don't create form input elements for Pictures/ValidSizes if you don't need them
and so on.
Update:
You assign different ViewModel not to a view, but to the controller action that handles link click. And from your questions it seems that what you need is not "oneway" binding, but rather avoiding extra characters in URL - because, if your ValidSizes is IEnumerable it won't be altered, and anyway in your URL its data is wrong, won't cause update - so it's already "one-way" binding.
This is what I can find for your problem:
http://forums.asp.net/t/1328683.aspx
As for solution, I never use ActionLink helper myself, because it is a leaky abstraction and I don't like to fight with it. Simple html link tag is always much better.
<a href="<%= Html.BuildUrlFromExpression<>() %>" />
I actually use my own few-lines version of the BuildUrlFromExpression for this. Also see in the link above how you can pass parameters via anonymous object instead of Model (new { PageSize = Model.PageSize, index = Model.index }).
Related
I Have a problem with my Jquery datatables. It shows only the first 10 rows out of 500 records in my database. Can someone help me to solve this problem.
Here is my code :
CONTROLLER :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.UI.WebControls;
using WinWin.Models;
namespace WinWin.Controllers
{
public class PostController : Controller
{
[Route("post")]
public ActionResult Index()
{
return View();
}
[Route("post/ajaxpost")]
public JsonResult ajaxpost(DTParameters param)
{
try
{
using (WintayoEntities db = new WintayoEntities())
{
var source = new List<Post>();
foreach (var p in db.ccp_post.ToList())
{
source.Add(new Post
{
id = p.post_id,
title = p.title,
date = p.post_on.ToString("yyyy-MM-dd"),
views = p.pageviews,
published = (p.is_published == 0) ? "Draft" : "Published",
featured = (p.is_featured == 0) ? "No" : "Yes",
action = string.Format(#"<i class=""fa fa-edit""></i> <i class=""fa fa-trash-o""></i>")
});
}
List<String> columnSearch = new List<String>();
foreach (var col in param.Columns)
{
columnSearch.Add(col.Search.Value);
}
List<Post> data = new ResultPost().GetResult(param.Search.Value, param.SortOrder, param.Start, param.Length, source, columnSearch);
int count = new ResultPost().Count(param.Search.Value, data, columnSearch);
DTResult<Post> result = new DTResult<Post>
{
draw = param.Draw,
data = data,
recordsFiltered = count,
recordsTotal = count
};
return Json(result);
}
}
catch (Exception e)
{
return Json(new { error = e.Message });
}
}
}
}
DATATABLE VIEW MODEL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WinWin.Models
{
/// <summary>
/// A full result, as understood by jQuery DataTables.
/// </summary>
/// <typeparam name="T">The data type of each row.</typeparam>
public class DTResult<T>
{
/// <summary>
/// The draw counter that this object is a response to - from the draw parameter sent as part of the data request.
/// Note that it is strongly recommended for security reasons that you cast this parameter to an integer, rather than simply echoing back to the client what it sent in the draw parameter, in order to prevent Cross Site Scripting (XSS) attacks.
/// </summary>
public int draw { get; set; }
/// <summary>
/// Total records, before filtering (i.e. the total number of records in the database)
/// </summary>
public int recordsTotal { get; set; }
/// <summary>
/// Total records, after filtering (i.e. the total number of records after filtering has been applied - not just the number of records being returned for this page of data).
/// </summary>
public int recordsFiltered { get; set; }
/// <summary>
/// The data to be displayed in the table.
/// This is an array of data source objects, one for each row, which will be used by DataTables.
/// Note that this parameter's name can be changed using the ajaxDT option's dataSrc property.
/// </summary>
public List<T> data { get; set; }
}
/// <summary>
/// The additional columns that you can send to jQuery DataTables for automatic processing.
/// </summary>
public abstract class DTRow
{
/// <summary>
/// Set the ID property of the dt-tag tr node to this value
/// </summary>
public virtual string DT_RowId
{
get { return null; }
}
/// <summary>
/// Add this class to the dt-tag tr node
/// </summary>
public virtual string DT_RowClass
{
get { return null; }
}
/// <summary>
/// Add this data property to the row's dt-tag tr node allowing abstract data to be added to the node, using the HTML5 data-* attributes.
/// This uses the jQuery data() method to set the data, which can also then be used for later retrieval (for example on a click event).
/// </summary>
public virtual object DT_RowData
{
get { return null; }
}
}
/// <summary>
/// The parameters sent by jQuery DataTables in AJAX queries.
/// </summary>
public class DTParameters
{
/// <summary>
/// Draw counter.
/// This is used by DataTables to ensure that the Ajax returns from server-side processing requests are drawn in sequence by DataTables (Ajax requests are asynchronous and thus can return out of sequence).
/// This is used as part of the draw return parameter (see below).
/// </summary>
public int Draw { get; set; }
/// <summary>
/// An array defining all columns in the table.
/// </summary>
public DTColumn[] Columns { get; set; }
/// <summary>
/// An array defining how many columns are being ordering upon - i.e. if the array length is 1, then a single column sort is being performed, otherwise a multi-column sort is being performed.
/// </summary>
public DTOrder[] Order { get; set; }
/// <summary>
/// Paging first record indicator.
/// This is the start point in the current data set (0 index based - i.e. 0 is the first record).
/// </summary>
public int Start { get; set; }
/// <summary>
/// Number of records that the table can display in the current draw.
/// It is expected that the number of records returned will be equal to this number, unless the server has fewer records to return.
/// Note that this can be -1 to indicate that all records should be returned (although that negates any benefits of server-side processing!)
/// </summary>
public int Length { get; set; }
/// <summary>
/// Global search value. To be applied to all columns which have searchable as true.
/// </summary>
public DTSearch Search { get; set; }
/// <summary>
/// Custom column that is used to further sort on the first Order column.
/// </summary>
public string SortOrder
{
get
{
return Columns != null && Order != null && Order.Length > 0
? (Columns[Order[0].Column].Data + (Order[0].Dir == DTOrderDir.DESC ? " " + Order[0].Dir : string.Empty))
: null;
}
}
}
/// <summary>
/// A jQuery DataTables column.
/// </summary>
public class DTColumn
{
/// <summary>
/// Column's data source, as defined by columns.data.
/// </summary>
public string Data { get; set; }
/// <summary>
/// Column's name, as defined by columns.name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Flag to indicate if this column is searchable (true) or not (false). This is controlled by columns.searchable.
/// </summary>
public bool Searchable { get; set; }
/// <summary>
/// Flag to indicate if this column is orderable (true) or not (false). This is controlled by columns.orderable.
/// </summary>
public bool Orderable { get; set; }
/// <summary>
/// Specific search value.
/// </summary>
public DTSearch Search { get; set; }
}
/// <summary>
/// An order, as sent by jQuery DataTables when doing AJAX queries.
/// </summary>
public class DTOrder
{
/// <summary>
/// Column to which ordering should be applied.
/// This is an index reference to the columns array of information that is also submitted to the server.
/// </summary>
public int Column { get; set; }
/// <summary>
/// Ordering direction for this column.
/// It will be dt-string asc or dt-string desc to indicate ascending ordering or descending ordering, respectively.
/// </summary>
public DTOrderDir Dir { get; set; }
}
/// <summary>
/// Sort orders of jQuery DataTables.
/// </summary>
public enum DTOrderDir
{
ASC,
DESC
}
/// <summary>
/// A search, as sent by jQuery DataTables when doing AJAX queries.
/// </summary>
public class DTSearch
{
/// <summary>
/// Global search value. To be applied to all columns which have searchable as true.
/// </summary>
public string Value { get; set; }
/// <summary>
/// true if the global filter should be treated as a regular expression for advanced searching, false otherwise.
/// Note that normally server-side processing scripts will not perform regular expression searching for performance reasons on large data sets, but it is technically possible and at the discretion of your script.
/// </summary>
public bool Regex { get; set; }
}
}
VIEW
<div class="contentpanel">
<div class="panel panel-default">
<div class="panel-body">
<div class="table-responsive">
<table class="table mb30 table-class-ajax" data-list="post/ajaxpost">
<colgroup>
<col style="width: 4%;" />
<col style="width: auto;" />
<col style="width: 13%;" />
<col style="width: 9%;" />
<col style="width: 8%;" />
<col style="width: 8%;" />
<col style="width: 105px;" />
</colgroup>
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Publish Date</th>
<th>Pageviews</th>
<th>Featured</th>
<th>Published</th>
<th></th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div><!-- contentpanel -->
<script>
$(document).ready(function () {
var oTable = $(".table-class-ajax").DataTable({
"serverSide": true,
"ajax": {
"type": "POST",
"url": "post/ajaxpost",
"contentType": 'application/json; charset=utf-8',
'data': function (data) { return data = JSON.stringify(data); }
},
"processing": true,
"paging": true,
"pagingType": "full_numbers",
"deferRender": true,
"columns": [
{ "data": "id" },
{ "data": "title" },
{ "data": "date" },
{ "data": "views" },
{ "data": "featured" },
{ "data": "published" },
{ "data": "action" }
],
"order": [0, "asc"],
"info": true,
});
});
</script>
You got paging turned on in your client side script for your DataTable.
//...
"paging": false
//...
In Your code if you see Datatable view model the lenth parameter states the total coloumns to be loaded for the data table.
/// <summary>
/// Number of records that the table can display in the current draw.
/// It is expected that the number of records returned will be equal to this number, unless the server has fewer records to return.
/// Note that this can b**e -1 to** indicate that all records should be returned (although that negates any benefits of server-side processing!)
/// </summary>
public int **Length** { get; set; }
Set the value to -1 as said in the code
I am generating HTML textbox through the html helper and TagBuilder.
we have the method TagBuilder.Attributes.Add("key","value")
but for HTML5 required attribute does not need value to be passed, so if i pass empty string then the output with value of required = ""
So how do i add required attribute without passing the value?
public static IHtmlString AppTextBox(this HtmlHelper helper, string model)
{
var input = new TagBuilder("input");
input.Attributes.Add("class", "form-control");
input.Attributes.Add("ng-model", model);
input.Attributes.Add("required","");
return new MvcHtmlString(input.ToString(TagRenderMode.Normal));
}
It's also valid to pass the name of the attribute as the value:
input.Attributes.Add("required", "required");
I've tested on MVC 5, not sure about older versions but the following does what you want.
tagBuilder.MergeAttribute("required", string.Empty);
Not sure if you still need an answer to this, but in the end I ended up writing a new class that derives from the base MVC tag builder. It's nothing too complex and I suspect there may be a few edge cases that this doesn't handle too well, but from the unit tests I have so far, it's pretty solid.
internal class ValuelessAttributeTagBuilder : System.Web.Mvc.TagBuilder
{
public List<string> ValuelessAttributes { get; private set; }
public ValuelessAttributeTagBuilder(string tagName) : base(tagName)
{
ValuelessAttributes = new List<string>();
}
public void AddValuelessAttribute(string value)
{
if(ValuelessAttributes.Contains(value))
ValuelessAttributes.Add(value);
}
/// <summary>
/// Renders the HTML tag by using the specified render mode.
/// </summary>
///
/// <returns>
/// The rendered HTML tag.
/// </returns>
/// <param name="renderMode">The render mode.</param>
public string ToString(TagRenderMode renderMode)
{
var sb = new StringBuilder();
switch (renderMode)
{
case TagRenderMode.StartTag:
sb.Append('<').Append(this.TagName);
AppendAttributes(sb);
AppendValuelessAttributes(sb);
sb.Append('>');
break;
case TagRenderMode.EndTag:
sb.Append("</").Append(this.TagName).Append('>');
break;
case TagRenderMode.SelfClosing:
sb.Append('<').Append(this.TagName);
AppendAttributes(sb);
AppendValuelessAttributes(sb);
sb.Append(" />");
break;
default:
sb.Append('<').Append(this.TagName);
AppendAttributes(sb);
AppendValuelessAttributes(sb);
sb.Append('>').Append(this.InnerHtml).Append("</").Append(this.TagName).Append('>');
break;
}
return sb.ToString();
}
private void AppendAttributes(StringBuilder sb)
{
foreach (KeyValuePair<string, string> keyValuePair in (IEnumerable<KeyValuePair<string, string>>)this.Attributes)
{
string key = keyValuePair.Key;
if (!string.Equals(key, "id", StringComparison.Ordinal) || !string.IsNullOrEmpty(keyValuePair.Value))
{
string str = HttpUtility.HtmlAttributeEncode(keyValuePair.Value);
sb.Append(' ').Append(key).Append("=\"").Append(str).Append('"');
}
}
}
private void AppendValuelessAttributes(StringBuilder sb)
{
foreach (var v in ValuelessAttributes)
{
var str = HttpUtility.HtmlAttributeEncode(v);
sb.Append(' ').Append(str);
}
}
}
I have a model class as shown below
public class AddKeyModel
{
public int Count { get; set; }
/// <summary>
/// 0 - Trial
/// 1 - License
/// </summary>
public bool LicenseType { get; set; }
/// <summary>
/// 0 - Lite
/// 1 - Pro
/// </summary>
public int ProductType { get; set; }
}
I am using DevExpress MVC extensions but facing problem in displaying this data on view. can anybody tell me the exact code to show radio buttons and also set back the data to model while submitting data to controller.
this code is not working
#Html.DevExpress().RadioButton(
settings =>
{
settings.Name = "Trial";
settings.GroupName = "LicenseType";
}).Bind(Model.LicenseType).GetHtml()
#Html.DevExpress().Label(
settings =>
{
settings.ControlStyle.CssClass = "label";
settings.Text = "License";
settings.AssociatedControlName = "License";
}).GetHtml()
#Html.DevExpress().RadioButton(
settings =>
{
settings.Name = "License";
settings.GroupName = "LicenseType";
}).Bind(Model.LicenseType).GetHtml()
You can use RadioButtonList instead.
See the How to use the CheckBoxList and RadioButtonList editors to edit Model fields example.
I am using a viewmodel with required field validation specified for some properties. I was able to create readonly version of same model using "displayfor". In a page, along with this readonly view there are other controls too along with submit. Now, when I click on "submit", it is getting validated and ModelState is invalid. How to remove validation, if we use model only for display.
ViewModel
public class CustomerVM
{
[Required]
public string Name {get;set;}
}
View
#using (Html.BeginForm("CreateCustomer, "Customer", FormMethod.Post))
{
#Html.DisplayFor(o => o.Name)
#..other input controls.#
<input id="btnSave" type="submit" value="Save" />
}
Model.State is invalid since name is rendered as label and httppost doesn't have that value.
This is where MetadataTypeAttribute comes in handy:
public class MyModel
{
public string Name { get; set; }
}
public interface IMyModelValidation
{
[Required]
public string Name { get; set; }
}
[MetadataType(typeof(IMyModelValiation))]
public class MyModelValidation : MyModel { }
Now MyModel has no validation and MyModelValidation does have validation, and they can be used almost interchangeably.
MetadataType
The MetadataTypeAttribute attribute enables you to associate a class with a data-model partial class. In this associated class you provide additional metadata information that is not in the data model.
For example, in the associated class you can apply the RequiredAttribute attribute to a data field. This enforces that a value is provided for the field even if this constraint is not required by the database schema.
You could use a different view model with different validation requirements for this 'read only' view. Or you could use the ModelState.Remove() method in your controller to get rid of errors against properties that you don't want validated. IMO the separate view model approach is better.
edit after seeing your code
Add a hiddenfor
#Html.DisplayFor(o => o.Name)
#Html.HiddenFor(o => o.Name)
That will pass the data back to the controller on the post and result in ModelState.IsValid == true
I'm not saying this is the best approach but I had to do something similar so I setup validation groups. I created an attribute that I placed on each model property that defined its validation group. Then on postback I called an extension method on the ViewDataDictionary and passed in the validation group that I wanted to run validation on. This would remove any validation messages for all other groups. Here is some example code:
The attribute:
/// <summary>
/// Attribute that assigns the property on a model to a given
/// validation group. By using the ValidationGroupExtension
/// and calling ValidateGroup on the ViewData validation errors
/// for properties that are not included in the validation group
/// will be removed.
/// </summary>
public class ValidationGroup : Attribute
{
/// <summary>
/// The unique name of the group.
/// </summary>
public String Group { get; set; }
public ValidationGroup(String group)
{
this.Group = group;
}
}
The extension:
/// <summary>
/// Used in conjunction with the ValidationGroup attribute to
/// specify which fields in a model should be validated. The
/// ValidateGroup extension should be called on ViewData before
/// checking whether the model is valid or not.
/// </summary>
public static class ValidationGroupExtension
{
/// <summary>
/// Remove all validation errors that are assocaited with
/// properties that do not have the ValidationGroup attribute
/// set with the specified group name.
///
/// This only handles flat models.
/// </summary>
/// <param name="viewData">View Data</param>
/// <param name="model">Data model returned</param>
/// <param name="group">Name of the validation group</param>
/// <returns></returns>
public static ViewDataDictionary ValidateGroup(this ViewDataDictionary viewData, Object model, String group)
{
//get all properties that have the validation group attribut set for the given group
var properties = model.GetType().GetProperties()
.Where(x => x.GetCustomAttributes(typeof(ValidationGroup), false)
.Where(a => ((ValidationGroup)a).Group == group).Count() > 0)
.Select(x => x.Name);
//find all properties that don't match these properties
var matchingProperties = viewData.ModelState.Where(x => !properties.Contains(x.Key)).ToList();
//remove any property that isn't in the gorup
foreach (var matchingProperty in matchingProperties)
{
viewData.ModelState.Remove(matchingProperty.Key);
}
return viewData;
}
}
On PostBack:
ViewData.ValidateGroup(model, "my validation group name");
if (ModelState.IsValid)
{
...
}
On the ViewModel:
[Required]
[DisplayName("Name")]
[ValidationGroup("my validation group name")]
public string Name { get; set; }
I'm trying to make a wizard in MVC3 using Entity Framework. It needs to keep the state of an object (an article in this case) across a couple of steps.
I have a static variable in my controller that instantiates a new Article. In the different Actions I use TryUpdateModel to map the form to the static variable. The problem is, it seems that TryUpdateModel() updates the database as well. I need TryUpdateModel to do the automatic mapping, and update the static _article variable, but I don't want it to persist to the database until the last step!
N.B: I know there are a lot of possible solutions for creating a wizard in MVC, but I'd like to know what to do to make this way work, so please no alternatives for an MVC wizard-pattern.
Thanks.
namespace website.Controllers
{
public class ArticlesController : BaseController
{
// private static variable to hold the chosen article in the wizard
private static articles _article = new articles();
/// <summary>
/// Index page shows a list of articles in a webgrid
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
List<articles> _articles = Data.getArticles();
return View(_articles);
}
/// <summary>
/// First page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult BasicDetails(string id, string nextButton)
{
// back or next doesn't matter - store form values
if (_article != null) TryUpdateModel(_article);
if (nextButton != null)
{
return RedirectToAction("ArticleGroup");
}
else
{
_article = Data.GetArticleById(id);
return View(_article);
}
}
/// <summary>
/// Second page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult ArticleGroup(string nextButton, string backButton)
{
TryUpdateModel(_article);
if (backButton != null)
return RedirectToAction("BasicDetails");
else if (nextButton != null)
{
return RedirectToAction("Price");
}
else
{
return View(_article);
}
}
/// <summary>
/// Third page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult Price(string nextButton, string backButton)
{
TryUpdateModel(_article);
if (backButton != null)
{
return RedirectToAction("ArticleGroup");
}
else if (nextButton != null)
return RedirectToAction("LinkedClubs");
else
{
return View(_article);
}
}
/// <summary>
/// Last page of the article wizard
/// </summary>
/// <returns></returns>
public ActionResult LinkedClubs(string backButton)
{
if (backButton != null)
return RedirectToAction("Price");
else
return View(_article);
}
}
}
Rather than using a static variable to hold your state information (that is a critical error btw) you should pass a state bag holding the information that you need in between pages.
Usually data entities (entities mapped to database) and viewmodel entities (entities with that user works) used separately. When user posted data after some step - you make TryUpdateModel() to session object (specific for user, not for all application as static variable).
At last step you call business logic method UpdateModel(viewmodel), that update all columns by id of viewmodel, using all filled properties.