I'm currently working on an Xamarin based app for iOS.
Among other features, the app is displaying items in a tableview (pretty classic). I noticed that the binding performance was quite bad, as scrolling really fast the table view will show some lag.
I've created a really simple project replicating the issue, available on Github.
To sum up briefly.
This project has an implementation for each of the following viewmodel interface:
public interface IItemViewModel : INotifyPropertyChanged, IActivatableViewModel
{
/// <summary>
/// A title
/// </summary>
string Title { get; }
/// <summary>
/// A value
/// </summary>
string Value { get; }
}
public interface IItemListViewModel : INotifyPropertyChanged, IActivatableViewModel
{
/// <summary>
/// Minimun count of items possible in the <see cref="Items"/> collection
/// </summary>
int MinItems { get; }
/// <summary>
/// Maximum count of items possible in the <see cref="Items"/> collection
/// </summary>
int MaxItems { get; }
/// <summary>
/// <see cref="Items"/>'s count.
/// Changing the value will update the <see cref="Items"/> collection accordingly.
/// </summary>
int ItemCount { get; set; }
ReadOnlyObservableCollection<IItemViewModel> Items { get; }
}
The view is implemented on iOS using one UIViewController displaying the VM's collection in a UITableView. A slider is bound to the VM's ItemCount property to update easily the item collection's count.
The ViewController is implemented as follow:
public partial class ViewController : ReactiveViewController<IItemListViewModel>
{
public ViewController(IntPtr intPtr) : base(intPtr)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.ItemTableView.RegisterNibForCellReuse(ItemViewCell.Nib, ItemViewCell.Key);
this.WhenActivated(disposables =>
{
this.Bind(this.ViewModel, vm => vm.ItemCount, v => v.ItemCountSlider.Value, v => (float)Convert.ToDouble(v), Convert.ToInt32)
.DisposeWith(disposables);
this.OneWayBind(this.ViewModel, vm => vm.ItemCount, v => v.ItemCountLabel.Text, Convert.ToString)
.DisposeWith(disposables);
this.WhenAnyValue(x => x.ViewModel.Items)
.BindTo<ItemViewModel, ItemViewCell>(this.ItemTableView, ItemViewCell.Key, 80, cell => cell.Initialize())
.DisposeWith(disposables);
});
this.ViewModel = new ItemListViewModel();
this.ItemCountSlider.MinValue = this.ViewModel.MinItems;
this.ItemCountSlider.MaxValue = this.ViewModel.MaxItems;
}
}
And the ViewCell rendering the IItemViewModel is:
public partial class ItemViewCell : ReactiveTableViewCell<IItemViewModel>
{
public static readonly NSString Key = new NSString("ItemViewCell");
public static readonly UINib Nib = UINib.FromName("ItemViewCell", NSBundle.MainBundle);
protected ItemViewCell(IntPtr handle) : base(handle)
{
// Note: this .ctor should not contain any initialization logic.
this.WhenActivated(disposables =>
{
this.OneWayBind(this.ViewModel, vm => vm.Title, v => v.TitleLabel.Text)
.DisposeWith(disposables);
this.OneWayBind(this.ViewModel, vm => vm.Value, v => v.ValueLabel.Text)
.DisposeWith(disposables);
});
}
public void Initialize()
{
}
}
When I deploy the app on an iPad 4, set the collection item count to something like few thousands, scrolling (very) fast become quite "laggy".
I know the device is old (built late 2015). But in my test project only two properties are bound to the ViewCell UI components.
In my real app, the viewmodel exposes around 10 properties, thus the lag appears as well on modern devices like the iPad pro.
I'm wondering if there is a better way to perform view cell bindings, or is it a technical limitation?
Thanks.
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 have a MVC5 application and set my hibernate stuff up like that:
public class PersistenceInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
//Nhibernate session factory
Component.For<ISessionFactory>().UsingFactoryMethod(CreateNhSessionFactory).LifeStyle.Singleton,
Component.For<ISession>().UsingFactoryMethod(k => k.Resolve<ISessionFactory>().OpenSession()).LifestylePerWebRequest(),
//All repoistories
Classes.FromAssembly(Assembly.GetAssembly(typeof(HdtRepository))).InSameNamespaceAs<HdtRepository>().WithService.DefaultInterfaces().LifestyleTransient()
);
}
and my base repository looks like that:
public abstract class RepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey> where TEntity : Entity<TPrimaryKey>
{
/// <summary>
/// Gets the NHibernate session object to perform database operations.
/// </summary>
public ISession Session { get; set; }
/// <summary>
/// Used to get a IQueryable that is used to retrive object from entire table.
/// </summary>
/// <returns>IQueryable to be used to select entities from database</returns>
public IQueryable<TEntity> GetAll()
{
return Session.Query<TEntity>();
}
/// <summary>
/// Gets an entity.
/// </summary>
/// <param name="key">Primary key of the entity to get</param>
/// <returns>Entity</returns>
public TEntity Get(TPrimaryKey key)
{
return Session.Get<TEntity>(key);
}
/// <summary>
/// Inserts a new entity.
/// </summary>
/// <param name="entity">Entity</param>
public void Insert(TEntity entity)
{
Session.Save(entity);
}
/// <summary>
/// Updates an existing entity.
/// </summary>
/// <param name="entity">Entity</param>
public void Update(TEntity entity)
{
Session.Update(entity);
}
/// <summary>
/// Deletes an entity.
/// </summary>
/// <param name="id">Id of the entity</param>
public void Delete(TPrimaryKey id)
{
Session.Delete(Session.Load<TEntity>(id));
}
}
Everything works corret when I Insert a entity.
Update is onyl working when I add a Session.Flush() to my Update method.
This is the mehtod which is called from Ajax and performs insert or update.
[Authorize]
[ValidateInput(false)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public JsonNetResult CreateOrUpdateTimeRecord(TimeRecord tr)
{
TimeRecord trLocal;
if (tr.Id == -1 || tr.Id == 0)
{
trLocal = new TimeRecord();
trLocal.Description = tr.Description;
trLocal.StartTime = tr.StartTime;
trLocal.EndTime = tr.EndTime;
trLocal.User = _userRepo.Get(tr.User.Id);
trLocal.Hdt = _hdtRepo.Get(tr.Hdt.Id);
_timeRepo.Insert(trLocal);
}
else
{
trLocal = _timeRepo.Get(tr.Id);
trLocal.Description = tr.Description;
trLocal.StartTime = tr.StartTime;
trLocal.EndTime = tr.EndTime;
_timeRepo.Update(trLocal);
}
return new JsonNetResult() { Data = trLocal};
}
I dont understand why this works for insert and not for Update.
Do I have to care about transaction and opening/closing Sessions?
I thought that "LifestylePerWebRequest" would do that for me.
Cheers,
Stefan
:edit: (my initial answer was partially wrong)
You implementation should actually work fine (just tested it).
Though I can reproduce the behavior that updates are not flushed although the session object gets disposed correctly
The LifestylePerWebRequest actually takes care of that just fine, it will dispose the session whenever the request ends. Therefore you had to add the request handle to the web.config etc... So the windsor stuff is perfectly aware of the fact that ISession is disposable and that it has to dispose it...
This is because of the the FlushMode of the session.
The default mode is Auto, which might not be really what you want because you cannot rely on the changes getting stored (especially with update calls).
You can either change the FlushMode of the created session object, or, and this is what I recommend, use transactions.
Lets look at the following example:
var session = container.Resolve<ISession>();
var obj = new Paper()
{
Author = "Author",
Description = "Description",
};
session.Save(obj);
obj.Author = "Author2";
session.Update(obj);
In this case, the update will never be flushed/stored in the database. This is the behavior you currently have I guess.
Now lets add a transaction around it:
var session = container.Resolve<ISession>();
using (var transaction = session.BeginTransaction())
{
var obj = new Paper()
{
Author = "Author",
Description = "Description",
};
session.Save(obj);
obj.Author = "Author2";
session.Update(obj);
transaction.Commit();
}
Now the changes will be saved for sure.
I have a class that I proxy it with Castle Dynamic Proxy. I want to add some custom Attributes to proxy methods (which is not defined in proxied class). Is this possible.
I want this because I want to generate ASP.NET Web API layer for my application's Service Layer. I proxied services (with inheriting from ApiController and additional IMyService interfaces), it works great but I want to add WebAPI specific attributes to this newly created Dynamic class, thus Web API framework can read them.
EDIT:
I want to explain detailed if someone want to know what I want actually.
public interface IMyService
{
IEnumerable<MyEntity> GetAll();
}
public class MyServiceImpl : IMyService
{
public IEnumerable<MyEntity> GetAll()
{
return new List<MyEntity>(); //TODO: Get from database!
}
}
public class MyServiceApiController : ApiController,IMyService
{
private readonly IMyService _myService;
public MyServiceApiController(IMyService myService)
{
_myService = myService;
}
public IEnumerable<MyEntity> GetAll()
{
return _myService.GetAll();
}
}
Think that I have a IMyService which is implemented by MyServiceImpl. And I want to make a Api controller to be able to use this service from web.
But as you see, api controller is just a proxy for real service. So, why I should write it? I can dynamically create it using castle windsor.
This is my idea and almost done it in my new project (https://github.com/hikalkan/aspnetboilerplate). But what if I need to add some attribute (such as Authorize) to GetAll method of the api controller. I cant directly add since there is no such a class, it's castle dynamic proxy.
So, beside this problem. I want to know if it's possible to add a attribute to a method of a synamic proxy class.
See again that project
https://github.com/aspnetboilerplate/aspnetboilerplate/issues/55
I also want to know how, so that I can define RoutePrefix on IService and Route on Action.
Fortunately, I finally know how to define custom attributes for proxy.
public class CustomProxyFactory : DefaultProxyFactory
{
#region Overrides of DefaultProxyFactory
protected override void CustomizeOptions(ProxyGenerationOptions options, IKernel kernel, ComponentModel model, object[] arguments)
{
var attributeBuilder = new CustomAttributeBuilder(typeof(DescriptionAttribute).GetConstructor(new[] { typeof(string) }), new object[] { "CustomizeOptions" });
options.AdditionalAttributes.Add(attributeBuilder);
}
#endregion
}
/// <summary>
/// 用户信息服务
/// </summary>
[Description("IUserInfoService")]
public interface IUserInfoService
{
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Description("IUserInfoService.GetUserInfo")]
UserInfo GetUserInfo([Description("IUserInfoService.GetUserInfo name")] string name);
}
/// <summary>
/// 用户信息服务
/// </summary>
[Description("UserInfoService")]
public class UserInfoService : IUserInfoService
{
/// <summary>
/// 获取用户信息
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[Description("UserInfoService.GetUserInfo")]
public virtual UserInfo GetUserInfo([Description("UserInfoService.GetUserInfo name")] string name)
{
return new UserInfo { Name = name };
}
}
using DescriptionAttribute = System.ComponentModel.DescriptionAttribute;
[TestFixture]
public class AttributeTests
{
/// <summary>
/// Reference to the Castle Windsor Container.
/// </summary>
public IWindsorContainer IocContainer { get; private set; }
[SetUp]
public void Initialize()
{
IocContainer = new WindsorContainer();
IocContainer.Kernel.ProxyFactory = new CustomProxyFactory();
IocContainer.Register(
Component.For<UserInfoService>()
.Proxy
.AdditionalInterfaces(typeof(IUserInfoService))
.LifestyleTransient()
);
}
/// <summary>
///
/// </summary>
[Test]
public void GetAttributeTest()
{
var userInfoService = IocContainer.Resolve<UserInfoService>();
Assert.IsNotNull(userInfoService);
var type = userInfoService.GetType();
Assert.IsTrue(type != typeof(UserInfoService));
var attribute = type.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
var method = type.GetMethod("GetUserInfo");
attribute = method.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
var parameter = method.GetParameters().First();
attribute = parameter.GetCustomAttribute<DescriptionAttribute>();
Assert.IsTrue(attribute != null);
Trace.WriteLine(attribute.Description);
}
}
#hikalkan I faced the same problem and I was looking for a solution as well. The best I could encounter was
How to add an attribute to a property at runtime
Proxy the controller (in my case a dynamic controller) with a new wrapper that set those attributes in the class itself and its methods..
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 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 }).