I try to Lazy< SelectList > for lazy caching any lookup data in my ASP.NET MVC project. But I cannot force Lazy object to reload lookup data when it is changed.
I create derived class like the following code. I found that Lazy< T > use IsValueCreated property to keep current state. However, in MappingFunc method I cannot change value of IsValueCreated because it is static method.
public class LazySelectList : Lazy<SelectList>
{
public LazySelectList(Func<LimeEntities, IEnumerable> initFn, string dataValueField, string dataTextField)
: base(MapingFunc(initFn, dataValueField, dataTextField))
{
}
public new bool IsValueCreated { get; set; }
public static Func<SelectList> MapingFunc(Func<DbContext, IEnumerable> valueFactory, string dataValueField, string dataTextField)
{
return () =>
{
var context = ObjectFactory.GetInstance<DbContext>();
return new SelectList(valueFactory(context), dataValueField, dataTextField);
};
}
}
I use the below code the call this function. But it always creates new value because IsValueCreated value is always false.
LookupCache.DocTypeList = new LazySelectList(db => db.DocTypes.OrderBy(x => x.Name), "ID", "Name");
After a several hours for searching & testing, I think it is impossible to reset state of lazy object. But I can create wrapper for handling this problem. The wrapper class contains lazy object and necessary object for creating new lazy object. The code should be like this.
public class LazyCache<TSource, TModel> : IValue<TModel>
{
private Lazy<TModel> _lazyObj;
private readonly Func<TSource, TModel> _valueFactory;
protected LazyCache()
{
Reset();
}
public LazyCache(Func<TSource, TModel> valueFactory) : this()
{
_valueFactory = valueFactory;
}
public void Reset()
{
_lazyObj = new Lazy<TModel>(MapingFunc());
}
public TModel Value
{
get { return _lazyObj.Value; }
}
protected virtual Func<TModel> MapingFunc()
{
return () =>
{
var context = ObjectFactory.GetInstance<TSource>();
return _valueFactory(context);
};
}
}
The above code allows us to reset state of object to force it to retrieve new data for defined function.
After that, I try to use the above method to cache SelectList object in ASP.NET MVC. But it always retrieves new from database because SelectList will contain IEnumerable object instead of real object data. So, I solve problem by enumerating data into temp object list like the following class.
public class LazyList<TSource> : LazyCache<TSource, SelectList>
{
private readonly Func<TSource, IEnumerable> _valueFactory;
private readonly string _dataValueField;
private readonly string _dataTextField;
public LazyList(Func<TSource, IEnumerable> valueFactory, string dataValueField, string dataTextField)
{
_valueFactory = valueFactory;
_dataValueField = dataValueField;
_dataTextField = dataTextField;
}
protected override Func<SelectList> MapingFunc()
{
return () =>
{
var context = ObjectFactory.GetInstance<TSource>();
// Force to retrieve data from current IEnumerable to prevent lazy loading data that
// cause system always connect to database every time they generate data from selectlist.
var loop = _valueFactory(context).GetEnumerator();
var tempList = new List<object>();
while (loop.MoveNext())
{
tempList.Add(loop.Current);
}
return new SelectList(tempList, _dataValueField, _dataTextField);
};
}
}
PS. All source code are a part of my Higgs RIA framework that available on Codeplex website.
LazyCache.cs | LazyList.cs
Related
This question has been asked before on SO and elsewhere in the context of MVC3 and there are bits and bobs about it related to ASP.NET Core RC1 and RC2 but niot a single example that actually shows how to do it the right way in MVC 6.
There are the following classes
public abstract class BankAccountTransactionModel {
public long Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public readonly string ModelType;
public BankAccountTransactionModel(string modelType) {
this.ModelType = modelType;
}
}
public class BankAccountTransactionModel1 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel1():
base(nameof(BankAccountTransactionModel1)) {}
}
public class BankAccountTransactionModel2 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel2():
base(nameof(BankAccountTransactionModel2)) {}
}
In my controller I have something like this
[Route(".../api/[controller]")]
public class BankAccountTransactionsController : ApiBaseController
{
[HttpPost]
public IActionResult Post(BankAccountTransactionModel model) {
try {
if (model == null || !ModelState.IsValid) {
// failed to bind the model
return BadRequest(ModelState);
}
this.bankAccountTransactionRepository.SaveTransaction(model);
return this.CreatedAtRoute(ROUTE_NAME_GET_ITEM, new { id = model.Id }, model);
} catch (Exception e) {
this.logger.LogError(LoggingEvents.POST_ITEM, e, string.Empty, null);
return StatusCode(500);
}
}
}
My client may post either BankAccountTransactionModel1 or BankAccountTransactionModel2 and I would like to use a custom model binder to determine which concrete model to bind based on the value in the property ModelType which is defined on the abstract base class BankAccountTransactionModel.
Thus I have done the following
1) Coded up a simple Model Binder Provider that checks that the type is BankAccountTransactionModel. If this is the case then an instance of BankAccountTransactionModelBinder is returned.
public class BankAccountTransactionModelBinderProvider : IModelBinderProvider {
public IModelBinder GetBinder(ModelBinderProviderContext context) {
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var type1 = context.Metadata.ModelType;
var type2 = typeof(BankAccountTransactionModel);
// some other code here?
// tried this but not sure what to do with it!
foreach (var property in context.Metadata.Properties) {
propertyBinders.Add(property, context.CreateBinder(property));
}
if (type1 == type2) {
return new BankAccountTransactionModelBinder(propertyBinders);
}
}
return null;
}
}
2) Coded up the BankAccountTransactionModel
public class BankAccountTransactionModelBinder : IModelBinder {
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public BankAccountTransactionModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders){
this._propertyBinders = propertyBinders;
}
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// I would like to be able to read the value of the property
// ModelType like this or in some way...
// This does not work and typeValue is...
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
// then once I know whether it is a Model1 or Model2 I would like to
// instantiate one and get the values from the body of the Http
// request into the properties of the instance
var model = Activator.CreateInstance(type);
// read the body of the request in some way and set the
// properties of model
var key = some key?
var result = ModelBindingResult.Success(key, model);
// Job done
return Task.FromResult(result);
}
}
3) Lastly I register the provider in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new BankAccountTransactionModelBinderProvider());
options.Filters.Add(typeof (SetUserContextAttribute));
});
The whole thing seems OK in that the provider is actually invoked and the same is the case for the model builder. However, I cannot seem to get anywhere with coding the logic in BindModelAsync of the model binder.
As already stated by the comments in the code, all that I'd like to do in my model binder is to read from the body of the http request and in particular the value of ModelType in my JSON. Then on the bases of that I'd like to instantiate either BankAccountTransactionModel1 or BankAccountTransactionModel and finally assign values to the property of this instance by reading them of the JSON in the body.
I know that this is a only a gross approximation of how it should be done but I would greatly appreciate some help and perhaps example of how this could or has been done.
I have come across examples where the line of code below in the ModelBinder
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
is supposed to read the value. However, it does not work in my model binder and typeValue is always something like below
typeValue
{}
Culture: {}
FirstValue: null
Length: 0
Values: {}
Results View: Expanding the Results View will enumerate the IEnumerable
I have also noticed that
bindingContext.ValueProvider
Count = 2
[0]: {Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProvider}
[1]: {Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider}
Which probably means that as it is I do not stand a chance to read anything from the body.
Do I perhaps need a "formatter" in the mix in order to get desired result?
Does a reference implementation for a similar custom model binder already exist somewhere so that I can simply use it, perhaps with some simple mods?
Thank you.
KeyMaster Model
public class KeyMasterModel
{
public int KeyId { get; set; }
public int TypeId { get; set; }
public string TypeName { get; set;}
}
PagedKeyModel
public class PagedKeyModel
{
// Collection of our KeyMasterModel , trying to populate this and send it to ViewModel to display a Grid//
public IPagedList<Security.Models.KeyMasterModel> pagedkeymaster;
}
//ViewModel//
public class KeyMasterViewModel
{
//tell me how to initialise the KeyMasterViewModel.pagedkeymodel.pagedkeymaster in a constructor here so that I dont get a null
public PagedKeyModel pagedkeymodel;
public KeyMasterModel keymastermodel;
}
//Controller//
[HttpGet]
public ActionResult ListOfKey(string sortOrder, string CurrentSort, int? page)
{
// View Model Object //
KeyMasterViewModel keyMasterViewModel = new KeyMasterViewModel();
// At the gollowing step keyMasterModelObject has values retrieved from db //
//Next aim is to place it into ViewModel Object by assigning the retrieved PagedList of KeyMasterModel to IPagedList<Security.Models.KeyMasterModel> pagedkeymaster
IPagedList<KeyMasterModel> KeyMasterModelObject= datalayercall.GetAll(sortOrder, CurrentSort, page);
// Here is where error is thrown , all of a sudden I get the error , KeyMasterModelObject becomes null .
// I am trying finally to populate everything into ViewModel object
keyMasterViewModel .pagedkeymodel.pagedkeymaster = KeyMasterModelObject;
return View(keyMasterViewModel);
}
// Business Logic Layer
public IPagedList<KeyMasterModel> GetAll(string sortOrder, string CurrentSort, int? page)
{
var retrieveddatalayerobject = datalayerobject.KeyMasters;
// Retrieving Data from db and forming a list according to my model in App//
List<KeyMasterModel> keymastermodellist = new List<KeyMasterModel>();
KeyMasterModel keymastermodelobject=new KeyMasterModel();
foreach(var retrieveditems in retrieveddatalayerobject)
{
keymastermodelobject.KeyId = retrieveditems.KeyId;
keymastermodelobject.TypeName = retrieveditems.TypeMaster.TypeName;
// Create a New List Of KeyMasterModel //
keymastermodellist.Add(keymastermodelobject);
}
// Paged List of KeyMaster Model //
IPagedList<KeyMasterModel> IPagedListKeyMasterModel = null;
switch (sortOrder)
{
case "KeyId":
if (sortOrder.Equals(CurrentSort))
IPagedListKeyMasterModel = keymastermodellist.OrderByDescending
(m => m.KeyId).ToPagedList(pageIndex, pageSize);
}
return IPagedListKeyMasterModel;
}
Your code as I understand it:
KeyMasterViewModel keyMasterViewModel = new KeyMasterViewModel();
[...]
keyMasterViewModel.pagedkeymodel.pagedkeymaster = KeyMasterModelObject;
So here you are using keyMasterViewModel.pagedkeymodel and assuming it is not null (because you are trying to assign to a property on it). However KeyMasterViewModel doesn't have a constructor and doesn't set pagedkeymodel to anything at declaration so it will default to being null. Hence your problem.
I'm using a third party reporting engine (stimulsoft) that calls an action on a controller via POST. Inside of the form, many fields are sent for the mechanics of the third party. Inside of the action I need some parameters all my parameters are inside of the URL.
I want to be able to use the model binder inside of my action.
At the moment I'm getting each fields one by one using this methods
var queryString = HttpUtility.ParseQueryString(Request.UrlReferrer.Query);
var preparedBy = queryString["preparedBy"];
var preparedAt = (queryString["preparedAt"] != null) ? Convert.ToDateTime(queryString["preparedAt"]) : DateTime.Today;
I would prefer to use a model and binding using the UrlReferrer. I've created a UrlReferrerValueProvider to bind from the action. I've tried that, but I'm getting a NullReferenceException on binder.BindModel line
public class UrlReferrerValueProvider : NameValueCollectionValueProvider
{
public UrlReferrerValueProvider(ControllerContext controllerContext)
: base(HttpUtility.ParseQueryString(controllerContext.HttpContext.Request.UrlReferrer.Query), CultureInfo.InvariantCulture)
{
}
}
public ActionResultat GetReportSnapshot()
{
var bindingContext = new ModelBindingContext()
{
ValueProvider = new UrlReferrerValueProvider(ControllerContext),
ModelName = "MyReportModel",
FallbackToEmptyPrefix = true
};
var binder = new DefaultModelBinder();
var myReportModel = binder.BindModel(ControllerContext, bindingContext);
[...]
return new EmptyResult();
}
public class MyReportModel
{
public string PreparedBy {get;set;}
public DateTime PreparedAt {get;set;}
}
Edited based on comments.
public class MyReportModel
{
public string PreparedBy {get;set;}
public DateTime PreparedAt {get;set;}
}
public class UrlReferrerValueProvider : NameValueCollectionValueProvider
{
public UrlReferrerValueProvider(ControllerContext controllerContext)
: base(HttpUtility.ParseQueryString(controllerContext.HttpContext.Request.UrlReferrer.Query), CultureInfo.InvariantCulture)
{
}
}
public ActionResult GetReportSnapshot(MyReportModel model)
{
this.UpdateModel(model, new UrlReferrerValueProvider(ControllerContext));
return new EmptyResult();
}
I have the following type hierarchy for ClientIndexModel:
public class ViewModel
{
public virtual IDictionary<string, SelectList> SelectListDictionary
{
get
{
var props = GetType().GetProperties().Where(p => p.PropertyType == typeof(SelectList));
return props.ToDictionary(prop => prop.Name, prop => (SelectList)prop.GetValue(this, null));
}
}
}
public class IndexModel<TIndexItem, TEntity> : ViewModel where TIndexItem : ViewModel where TEntity : new()
{
public List<TIndexItem> Items { get; private set; }
}
public class ClientIndexModel: IndexModel<ClientIndexItem, Client>
{
}
I instantiate in and return a ClientIndexModel from an ApiController as follows:
public ClientIndexModel Get()
{
var model = new ClientIndexModel();
return model;
}
If I inspect model with a breakpoint on the return model; line, the Items property is present, with a count of 0. Yet the JSON returned from this action only has the SelectListDictionary property and no Items property. Why could this be?
Your Items property has a private setter. Properties with private setters are intentionally omitted from serialization as it makes no sense to serialize them because they can never be deserialized back as their values cannot be modified from the outside. So you should either completely remove the setter (as you have done for the SelectListDictionary property), make it public or write a custom formatter using some custom serializer that is capable of serializing properties with private setters.
I have an action method like this in my controller
public ActionResult Index()
{
using (NorthwindDataContext db = new NorthwindDatacontext())
{
var results = db.GetRecordSets(arg1, ....).ToList();
// use results as list
}
return View();
}
and I wanted to start making tests for it (yes, after it was built, not before... but the code was written before I started to use TDD so... )
and I figured out that adding a property such as this one to the controller
public delegate NorthwindDatacontext ContextBuilderDelegate();
public ContextBuilderDelegate ContextBuilder { get; set; }
I could add in the constructor something like this...
ContextBuilder = () => new NorthwindDatacontext();
then I could test the ActionMethod setting the ContextBuilder property with a mock of NorthwindDatacontext
var controller = new MyController();
var mockDataContext = new Mock<NorthwindDatacontext>();
controller.ContextBuilder = () => mockDataContext.Object;
But... I found no way to use this because all methods of NorthwindDatacontext use ISingleResult as returnType and I cant find the way to create an object with that interface.
I've tried this
var theResult = new List<GetRecordSetsResult>();
// fill the data structure here with the provided result...
mockDataContext.Setup(c => c. GetRecordSets()).Returns(theResult as
ISingleResult<GetRecordSetsResult>);
but it doesn't work because theResult is null when converted to ISingleResult.
Is there any way to create a ISingleResult object to test this way or I'm doing the incorrect way to do things here?
Thanks in Advance
ToList() is an extension method for IEnumerable, which is easy to mock, because it only has one member method -- GetEnumerator().
Still you might have problems mocking NorthwindDataContext class, if its methods are not virtual...
Anyways, that's how I solved a similar problem in my sandbox, hope it helps:
public class MyType
{
public virtual ISingleResult<int> ReturnSomeResult() { throw new NotImplementedException(); }
}
[TestMethod]
public void TestMethod1()
{
var mockMyType = new Mock<MyType>();
var mockSingleResult = new Mock<ISingleResult<int>>();
IEnumerable<int> someEnumerable = new int[] {1,2,3,4,5};
mockSingleResult.Setup(result => result.GetEnumerator()).Returns(someEnumerable.GetEnumerator());
mockMyType.Setup(myType => myType.ReturnSomeResult()).Returns(mockSingleResult.Object);
Assert.AreEqual(15, mockMyType.Object.ReturnSomeResult().ToList().Sum());
}
I created a class that implemented ISingleResult and just put a List in it. I am fairly new to this type of coding, so while this worked for me, use at your own risk (and if you see holes post a comment).
class SingleResult<T>:ISingleResult<T>
{
readonly List<T> _list = new List<T>();
public void Add(T item)
{
_list.Add(item);
}
#region Interface Items
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public object ReturnValue { get { return _list; } }
public void Dispose() { }
#endregion
}
This can then be used to return in part of a mock. This is how I ended up using it with Rhino Mocks:
[TestMethod]
public void TestSomething()
{
//Arrange
// Make a data context and DAL
var _ctx = MockRepository.GenerateMock<IDataClassesDataContext>();
var someDALClass = new SomeDALClass(_ctx);
User testUser = UserObjectHelper.TestUser();
SingleResult<User> userList = new SingleResult<User> { testUser };
// Indicate that we expect a call the to sproc GetUserByUserID
_ctx.Expect(x => x.GetUserByUserID(testUser.UserID)).Return(userList);
//Act
someDALClass.UpdateUser(testUser);
//Assert
Assert.IsTrue(SomeTestCondition());
}