Calling a 'non-static string' from ouside the individual page model - c#

I'm using .net core Razor pages, and I'm trying to write a class / function to look up CMS data from database. I can get it work if I put the class into the model.cs of each page, but I really want to just call it from a central class file.
The code that I have that works is:
/// on the .cs model page:
namespace TestFrontEnd.Pages
{
public class IndexModel : PageModel
{
private readonly TestFrontEnd.Data.EventContext _context;
public IndexModel(TestFrontEnd.Data.EventContext context)
{
_context = context;
}
public string GetByTitle(string CMSTitle, int PageType = 0)
{
var CMSEntry = _context.CMSFieldList
.Where(a => a.Title.ToLower() == CMSTitle.ToLower())
.Include(a => a.CMSEntries.Where(b => b.PageType == PageType ))
.AsNoTracking()
.FirstOrDefault();
string ReturnText = "";
if (CMSEntry != null)
{
if (CMSEntry.CMSEntries.Any())
{
ReturnText = CMSEntry.CMSEntries.First().CEntry;
}
}
return ReturnText;
}
}
}
//On the Index.cshtml page:
#Model.GetByTitle("MainHeader")
This works just as I want it to - looks up the CMS entry in my database with the Title 'MainHeader' and returns the entry into the page. What I want to do is be able to call this on any page without having to copy the same 'public string GetByTitle...' code into each page model. I've created the following EventCMS class page:
namespace TestFrontEnd.Pages
{
public class EventCMS
{
private readonly TestFrontEnd.Data.EventContext _context;
public EventCMS(TestFrontEnd.Data.EventContext context)
{
_context = context;
}
public string GetByTitle(string CMSTitle, int PageType = 0)
{
var CMSEntry = _context.CMSFieldList
.Where(a => a.Title.ToLower() == CMSTitle.ToLower())
.Include(a => a.CMSEntries.Where(b => b.PageType == PageType ))
.AsNoTracking()
.FirstOrDefault();
string ReturnText = "";
if (CMSEntry != null)
{
if (CMSEntry.CMSEntries.Any())
{
ReturnText = CMSEntry.CMSEntries.First().CEntry;
}
}
return ReturnText;
}
}
}
I want to call it on a Razor page using something like #EventCMS.GetByTitle("MainHeader") but I get a 'CS0120 : An object reference is required for the non-static field, method or property' error.
If I make it a static string without the database lookup it works fine
public static string GetByTitle(string CMSTitle, int PageType = 0)
{
string ReturnText = "";
if (CMSTitle.ToLower() == "mainheader")
{
ReturnText = "This is my static main header";
}
return ReturnText;
}
I'm obviously missing something fundamental, but I'm quite new to .net / c#, previously having written in classic ASP, so if anyone can point me in the right direction for achiveing this, that would be much appricated!

In your Startup.cs file, in the ConfigureServices method, you have to register the dependency for EventCMS:
services.AddSingleton<EventCMS>();
than you have to inject this dependency in all the classes who need to use the methods of this class:
namespace TestFrontEnd.Pages
{
public class IndexModel : PageModel
{
private readonly TestFrontEnd.Data.EventContext _context;
public EventCMS EventCMS { get; }
public IndexModel(TestFrontEnd.Data.EventContext context, EventCMS eventCMS)
{
_context = context;
EventCMS = eventCMS;
}
}
}
And than you can use it in your cshtml
//On the Index.cshtml page:
#Model.EventCMS.GetByTitle("MainHeader")

Related

NSubstitute and DbContext.Set<TEntity> Could not find a call to return from

I am trying to write some tests for an existing service we have. It uses the DbContext (In our case, named DatabaseContext) and the constructor looks like this:
public GenericOrderProvider(DatabaseContext context, IOrderHandler<T> orderHandler)
{
_orderHandler = orderHandler;
_context = context;
_dbSet = context.Set<T>();
}
As you can see, it's generic and sets the _dbSet when it's initialized.
I have this very simple method:
public Attempt<IQueryable<T>> List(params string[] includes)
{
var x = _dbSet.ToList();
return Attempt<IQueryable<T>>.Succeed(_dbSet.OrderBy(m => m.Order));
}
And I wrote this test:
[TestFixture]
public class ListShould
{
[Test]
public void ReturnList()
{
// Assemble
var services = GenericOrderProviderContext.GivenServices();
var provider = services.WhenCreateOrderProvider();
services.DatabaseContext.Set<Attribute>().ReturnsForAnyArgs(new List<Attribute>().ToDbSet());
//services.DatabaseContext.Attributes = new List<Attribute>().ToDbSet();
// Act
var result = provider.List();
// Assert
result.Failure.Should().BeFalse();
result.Result.Count().Should().Be(0);
}
}
When I run that test, I get the error:
NSubstitute.Exceptions.CouldNotSetReturnDueToNoLastCallException : Could not find a call to return from.
The trace specifically targets the line services.DatabaseContext.Set<Attribute>().ReturnsForAnyArgs(new List<Attribute>().ToDbSet()); but I have no idea how to fix it.
As far as I can tell, I am mapping to the right method.
For completeness, here is my test Contexts:
public class GenericOrderProviderContext: DatabaseContextContext<GenericOrderProviderContext>
{
public static GenericOrderProviderContext GivenServices() => new GenericOrderProviderContext();
public IGenericOrderProvider<Attribute> WhenCreateOrderProvider() =>
new GenericOrderProvider<Attribute>(DatabaseContext,
new OrderHandler<Attribute>(DatabaseContext));
public bool IsSequential(List<Attribute> models)
{
return !models.OrderBy(m => m.Order).Select(m => m.Order).Select((i, j) => i - j).Distinct().Skip(1).Any();
}
}
public class DatabaseContextContext<T> where T: DatabaseContextContext<T>
{
public DatabaseContext DatabaseContext;
protected DatabaseContextContext()
{
DatabaseContext = Substitute.For<DatabaseContext>();
}
}
Does anyone know what I can do to resolve this issue?

how to perform a runtime view update when using ResourceViewLocationProvider

Im runing a nancyfx with owin on centos 6.5 with mono 5.10.0.140, I change the default ViewLocationProvider to ResourceViewLocationProvider for the default ViewLocationProvider causes memory leak of somekind after running for days, and the ResourceViewLocationProvider dont have the same problem. I would like to hot update Views just like what we can do with a default ViewLocationProvider, but it seems impossibe when googling around.
I did find a partial solution though, by implenting a custom IViewLocator and a IViewCache, I did achieve someking of hot update. But It didn`t feel right aside from those ugly static class
//Here is what I did in the custom IViewLocator
//...class definition fallback viewlocator and other staffs
private static ConcurrentDictionary<string, ViewLocationResult> _cachedViewLocationResults;
//..other code
public ViewLocationResult LocateView(string viewName, NancyContext context)
{
//...lock and others
if (_cachedViewLocationResults != null && _cachedViewLocationResults.ContainsKey(viewName))
{
return _cachedViewLocationResults[viewName];
}
//...lock and others
return fallbackViewLocator.LocateView(viewName, context);
}
//...other class
//here is how I update Views
public static void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements)
{
lock (CacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new ConcurrentDictionary<string, ViewLocationResult>();
foreach (var replace in replacements)
{
_cachedViewLocationResults.AddOrUpdate(replace.Key, x=>replacements[x], (x,y)=>y);
}
}
}
//END OF IViewLocator
//here is what I did in the custom IViewCache
//another static for ViewCache to tell if the view has been updated
public static List<ViewLocationResult> Exceptions { get; private set; }
//...some other code
//here is how I ignore the old cache
public TCompiledView GetOrAdd<TCompiledView>(ViewLocationResult viewLocationResult, Func<ViewLocationResult, TCompiledView> valueFactory)
{
if (Exceptions.Any(x=>x.Name == viewLocationResult.Name && x.Location == viewLocationResult.Location && x.Extension == viewLocationResult.Extension))
{
object old;
this.cache.TryRemove(viewLocationResult, out old);
Exceptions.Remove(viewLocationResult);
}
return (TCompiledView)this.cache.GetOrAdd(viewLocationResult, x => valueFactory(x));
}
With those implentions and a little bit of settings on the bootstrapper plus a router for some mysql update, I can update the View the way I want, but here is the problem:
1. now I have to manually map all the Location,Name,Extension for the ViewLocationResult to use and there are too many of them (243...), I would like to use the some sort of built-in function to identify the changes, something like the IsStale function of the ViewLocationResult, but I didnt know which and how...
2. those static class are ugly and I think it could be problematic but I didnt know a better way to replace them.
Could some one kindly give me a hint, thank in advance.
Well, I finally figure out how to do this myself, just in case anyone else want to use the same method as I do, Here is how you update your view in memory:
Make a interface
public interface INewViewLocationResultProvider
{
bool UseCachedView { get; set; }
ViewLocationResult GetNewerVersion(string viewName, NancyContext context);
void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements);
}
Make a new ViewLocationResultProvider
public class ConcurrentNewViewLocationResultProvider : INewViewLocationResultProvider
{
private Dictionary<string, ViewLocationResult> _cachedViewLocationResults;
private readonly object _cacheLock = new object();
public bool UseCachedView { get; set; }
public ConcurrentNewViewLocationResultProvider()
{
lock (_cacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new Dictionary<string, ViewLocationResult>();
}
}
public ViewLocationResult GetNewerVersion(string viewName, NancyContext context)
{
if (UseCachedView)
{
if (Monitor.TryEnter(_cacheLock, TimeSpan.FromMilliseconds(20)))
{
try
{
if (_cachedViewLocationResults != null && _cachedViewLocationResults.ContainsKey(viewName))
{
return _cachedViewLocationResults[viewName];
}
}
finally
{
Monitor.Exit(_cacheLock);
}
}
}
return null;
}
public void UpdateCachedView(IDictionary<string, ViewLocationResult> replacements)
{
lock (_cacheLock)
{
if(_cachedViewLocationResults == null)_cachedViewLocationResults = new Dictionary<string, ViewLocationResult>();
foreach (var replace in replacements)
{
if (_cachedViewLocationResults.ContainsKey(replace.Key))
{
_cachedViewLocationResults[replace.Key] = replace.Value;
}
else
{
_cachedViewLocationResults.Add(replace.Key,replace.Value);
}
}
}
}
}
In your Bootstrapper,register the new ViewLocationResultProvider with tinyIoc or equivalent
container.Register<INewViewLocationResultProvider, ConcurrentNewViewLocationResultProvider>().AsSingleton();
Make a derived class from ViewLocationResult
public class OneTimeUsedViewLocationResult : ViewLocationResult
{
private bool _used = false;
public OneTimeUsedViewLocationResult(string location, string name, string extension, Func<TextReader> contents)
: base(location, name, extension, contents)
{
}
public override bool IsStale()
{
if (_used) return false;
_used = true;
return true;
}
}
And a new IViewLocator:
public class CachedViewLocator : IViewLocator
{
private readonly INewViewLocationResultProvider _newVersion;
private readonly DefaultViewLocator _fallbackViewLocator;
public CachedViewLocator(IViewLocationProvider viewLocationProvider, IEnumerable<IViewEngine> viewEngines, INewViewLocationResultProvider newVersion)
{
_fallbackViewLocator = new DefaultViewLocator(viewLocationProvider, viewEngines);
_newVersion = newVersion;
}
public ViewLocationResult LocateView(string viewName, NancyContext context)
{
if (_newVersion.UseCachedView)
{
var result = _newVersion.GetNewerVersion(viewName, context);
if (result != null) return result;
}
return _fallbackViewLocator.LocateView(viewName, context);
}
public IEnumerable<ViewLocationResult> GetAllCurrentlyDiscoveredViews()
{
return _fallbackViewLocator.GetAllCurrentlyDiscoveredViews();
}
}
}
Tell nancy about the new ViewLocator
protected override NancyInternalConfiguration InternalConfiguration
{
get
{
return NancyInternalConfiguration.WithOverrides
(
nic =>
{
nic.ViewLocationProvider = typeof(ResourceViewLocationProvider);//use this or your equivalent
nic.ViewLocator = typeof(CachedViewLocator);
}
);
}
}
Then you can update it through a API like this:
public class YourModule : NancyModule
{
public YourModule(INewViewLocationResultProvider provider)
{
Get["/yourupdateinterface"] = param =>
{
if(!provider.UseCachedView) return HttpStatusCode.BadRequest;//in case you turn off the hot update
//you can serialize your OneTimeUsedViewLocationResult with Newtonsoft.Json and store those views in any database, like mysql, redis, and load them here
//data mock up
TextReader tr = new StringReader(Resources.TextMain);
var vlr = new OneTimeUsedViewLocationResult("","index","cshtml",()=>tr);
var dir = new Dictionary<string, ViewLocationResult> {{"index",vlr}};
//mock up ends
provider.UpdateCachedView(dir);
return HttpStatusCode.OK;
}
}
}
Note: Those code above doesn't solve the manually map all the Location,Name,Extension for the ViewLocationResult thing menthions in my question, but since I endup build a view editor for my colleges to upload their views, I don't need to solve it anymore.

Navigation and DI

Im trying to make a standard code to use in order to implement in my xamarin.forms apps. What I want to do is to have a way to navigate between viewmodels and a wey to correctly implement dependency injection.
What I'm currently doing for navigation:
await Navigation.PushAsync(new SecondPageView());
And for DI:
var test = DependencyService.Get<ITestService>();
WelcomeMessage = test.GetSystemWelcome();
I know that the correct way to implement Di is creating an interface and proceed from that step but the problem is that when I try, I fail trying to have a good navigation system (like register the view and the view model in a file apart).
Does anyone have a sample example that I can have a look? Or maybe some indications in order to proceed?
PD: I trying to avoid frameworks like MvvMcross things like that.
Thanks in advance!
(I will try to simplify all the code examples as much as possible).
1. First of all we need a place where we could register all our objects and optionally define their lifetime. For this matter we can use an IOC container, you can choose one yourself. In this example I will use Autofac(it is one of the fastest available). We can keep a reference to it in the App so it will be available globally (not a good idea, but needed for simplification):
public class DependencyResolver
{
static IContainer container;
public DependencyResolver(params Module[] modules)
{
var builder = new ContainerBuilder();
if (modules != null)
foreach (var module in modules)
builder.RegisterModule(module);
container = builder.Build();
}
public T Resolve<T>() => container.Resolve<T>();
public object Resolve(Type type) => container.Resolve(type);
}
public partial class App : Application
{
public DependencyResolver DependencyResolver { get; }
// Pass here platform specific dependencies
public App(Module platformIocModule)
{
InitializeComponent();
DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
MainPage = new WelcomeView();
}
/* The rest of the code ... */
}
2.We will need an object responsible for retrieving a Page (View) for a specific ViewModel and vice versa. The second case might be useful in case of setting the root/main page of the app. For that we should agree on a simple convention that all the ViewModels should be in ViewModels directory and Pages(Views) should be in the Views directory. In other words ViewModels should live in [MyApp].ViewModels namespace and Pages(Views) in [MyApp].Views namespace. In addition to that we should agree that WelcomeView(Page) should have a WelcomeViewModel and etc. Here is a code example of a mapper:
public class TypeMapperService
{
public Type MapViewModelToView(Type viewModelType)
{
var viewName = viewModelType.FullName.Replace("Model", string.Empty);
var viewAssemblyName = GetTypeAssemblyName(viewModelType);
var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
return Type.GetType(viewTypeName);
}
public Type MapViewToViewModel(Type viewType)
{
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewModelAssemblyName = GetTypeAssemblyName(viewType);
var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
return Type.GetType(viewTypeModelName);
}
string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
string GenerateTypeName(string format, string typeName, string assemblyName) =>
string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}
3.For the case of setting a root page we will need sort of ViewModelLocator that will set the BindingContext automatically:
public static class ViewModelLocator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable) =>
(bool)bindable.GetValue(AutoWireViewModelProperty);
public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
bindable.SetValue(AutoWireViewModelProperty, value);
static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();
static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
var viewType = view.GetType();
var viewModelType = mapper.MapViewToViewModel(viewType);
var viewModel = (Application.Current as App).DependencyResolver.Resolve(viewModelType);
view.BindingContext = viewModel;
}
}
// Usage example
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
viewmodels:ViewModelLocator.AutoWireViewModel="true"
x:Class="MyApp.Views.MyPage">
</ContentPage>
4.Finally we will need a NavigationService that will support ViewModel First Navigation approach:
public class NavigationService
{
TypeMapperService mapperService { get; }
public NavigationService(TypeMapperService mapperService)
{
this.mapperService = mapperService;
}
protected Page CreatePage(Type viewModelType)
{
Type pageType = mapperService.MapViewModelToView(viewModelType);
if (pageType == null)
{
throw new Exception($"Cannot locate page type for {viewModelType}");
}
return Activator.CreateInstance(pageType) as Page;
}
protected Page GetCurrentPage()
{
var mainPage = Application.Current.MainPage;
if (mainPage is MasterDetailPage)
{
return ((MasterDetailPage)mainPage).Detail;
}
// TabbedPage : MultiPage<Page>
// CarouselPage : MultiPage<ContentPage>
if (mainPage is TabbedPage || mainPage is CarouselPage)
{
return ((MultiPage<Page>)mainPage).CurrentPage;
}
return mainPage;
}
public Task PushAsync(Page page, bool animated = true)
{
var navigationPage = Application.Current.MainPage as NavigationPage;
return navigationPage.PushAsync(page, animated);
}
public Task PopAsync(bool animated = true)
{
var mainPage = Application.Current.MainPage as NavigationPage;
return mainPage.Navigation.PopAsync(animated);
}
public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
InternalPushModalAsync(typeof(TViewModel), animated, parameter);
public Task PopModalAsync(bool animated = true)
{
var mainPage = GetCurrentPage();
if (mainPage != null)
return mainPage.Navigation.PopModalAsync(animated);
throw new Exception("Current page is null.");
}
async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
{
var page = CreatePage(viewModelType);
var currentNavigationPage = GetCurrentPage();
if (currentNavigationPage != null)
{
await currentNavigationPage.Navigation.PushModalAsync(page, animated);
}
else
{
throw new Exception("Current page is null.");
}
await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
}
}
As you may see there is a BaseViewModel - abstract base class for all the ViewModels where you can define methods like InitializeAsync that will get executed right after the navigation. And here is an example of navigation:
public class WelcomeViewModel : BaseViewModel
{
public ICommand NewGameCmd { get; }
public ICommand TopScoreCmd { get; }
public ICommand AboutCmd { get; }
public WelcomeViewModel(INavigationService navigation) : base(navigation)
{
NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
}
}
As you understand this approach is more complicated, harder to debug and might be confusing. However there are many advantages plus you actually don't have to implement it yourself since most of the MVVM frameworks support it out of the box. The code example that is demonstrated here is available on github. There are plenty of good articles about ViewModel First Navigation approach and there is a free Enterprise Application Patterns using Xamarin.Forms eBook which is explaining this and many other interesting topics in detail.

AutoMapper with Dataset as parameter in profile

I have some problem when using AutoMapper. I searched everywhere I could but either I don't understand the solution (when no code is provided) or it doesn't apply to my situation.
I have my profile :
public class MyCustomProfile : Profile
{
public MyCustomProfile()
{
CreateMap<DTO.Person, MyDataSet.PersonRow>();
}
}
And I have my method :
public static void TestAutoMapper(DTO.Person p)
{
if (Mapper.Instance == null)
{
Mapper.Initialize(cfg => cfg.AddProfile<MyCustomProfile>());
}
MyDataSet.PersonRow pr = Mapper.Map<MyDataSet.PersonRow>(p);
}
But my problem is :
I need to add in my profile .ConstructUsing(p => dts.get_XYdataset().Person.NewPersonRow()); where dts is an instance of MyDataSet.
And I also need this instance of MyDataSet dts in the method TestAutoMapper(DTO.Person p) to save the result MyDataSet.PersonRow pr as the following :
dts.get_XYdataset().Person.AddPersonRow(pr)
But I don't know what to do. It works well if I put everything in the TestAutoMapper() method but of course it's not clean and I want to separate logics by creating a profile and calling it when initializing the mapper.
EDIT
So I modified my TestAutoMapper() method like this :
public static void TestAutoMapper(DTO.Person p)
{
if (Mapper.Instance == null)
{
Mapper.Initialize(cfg => cfg.AddProfile<MyCustomProfile>());
}
using (MyDataSet dts = new MyDataSet())
{
MyDataSet.PersonRow pr = Mapper.Map<MyDataset.PersonRow>(p, opt => opt.Items["Dataset"] = dts);
dts.get_XYdataset().Person.AddPersonRow(pr);
}
Then I tried to follow the mini tuto about Custom Resolvers and implemented this class :
public class CustomResolver: IMemberValueResolver<object, object, MyDataSet, MyDataSet>
{
public MyDataSet Resolve(object source, object destination, MyDataSet dts, MyDataSet dts2, ResolutionContext context)
{
if (dts != null)
{
return dts;
}
else if (dts2 != null)
{
return dts2;
}
return new MyDataSet();
}
}
But I don't think this is okay. But well, nevermind I tried anyway. But now I'm stuck in my profile constructor in the CreateMap<DTO.Person, MyDataSet.PersonRow>() statement. How to get the Options.Items["Dataset"] in the .ConstructUsing() ?
Examples show about a CustomResolver about a member but how about specifying a constructor ?
It would be so perfect if I could do something like :
CreateMap<DTO.Person, MyDataSet.PersonRow>()
.ConstructUsing(Options.Items["Dataset"].get_XYdataset().Person.NewPersonRow());
I know my need for help can be silly but I really don't understand even by reading the docs.
What do you think ?
Thanks,
Hellcat8
Okay finally it works with this code :
public static class PersonManager
{
private static MapperConfiguration _config;
private static IMapper _mapper;
public static void TestAutoMapper(DTO.Person p)
{
if (_config == null)
{
_config = new MapperConfiguration(cfg => cfg.AddProfile<MyCustomProfile>());
}
if (_mapper == null)
{
_mapper = _config.CreateMapper();
}
using (MyDataSet dts = new MyDataSet())
{
XYdataset.PersonRow pr = _mapper.Map<XYdataset.PersonRow>(p, opt => opt.Items["Dataset"] = dts);
dts.get_XYdataset().Person.AddPersonRow(pr);
}
}
}
And I have my profile :
public class MyCustomProfile : Profile
{
public MyCustomProfile()
{
CreateMap<DTO.Person, XYdataset.PersonRow>()
.ConstructUsing((source, resolutionContext) =>
(resolutionContext.Items["Dataset"] as MyDataSet)
.(get_XYdataset().Person.NewPersonRow());
}
}

HttpSessionStateBase losing property values of inherited type

We are using HttpSessionStateBase to store messages in a set up similar to this working example:
public class HttpSessionMessageDisplayFetch : IMessageDisplayFetch
{
protected HttpSessionStateBase _session;
private IList<ICoreMessage> messages
{
get
{
if (_session[EchoCoreConstants.MESSAGE_KEY] == null)
_session[EchoCoreConstants.MESSAGE_KEY] = new List<ICoreMessage>();
return _session[EchoCoreConstants.MESSAGE_KEY] as IList<ICoreMessage>;
}
}
public HttpSessionMessageDisplayFetch()
{
if (HttpContext.Current != null)
_session = new HttpSessionStateWrapper(HttpContext.Current.Session);
}
public void AddMessage(ICoreMessage message)
{
if (message != null)
messages.Add(message);
}
public IEnumerable<IResultPresentation> FlushMessagesAsPresentations(IResultFormatter formatter)
{
var mToReturn = messages.Select(m => m.GetPresentation(formatter)).ToList();
messages.Clear();
return mToReturn;
}
}
When we pass in a QualityExplicitlySetMessage (which inherits from ICoreMessage, see below) it is saved correctly to messages.
This is how the object looks after being inserted into the messages list, at the end of AddMessage(ICoreMessage message) above.
But when we come to access it after changing controllers the inherited member's properties are null, which causes a variety of null reference exceptions.
This is how the object now looks after we call FlushMessagesAsPresentations. I've commented out var mToReturn... as this tries to access one of these null ref properties.
I'd like to ask the following:
Why is the HttpSessionStateBase failing to capture these values taken
by the inherited type?
Is this an issue in saving to the HttpSession or in retrieving?
Is this anything to do with, as I suspect, inheritance?
Or is the fact I'm potentially calling a new controller that dependency injects the HttpSessionMessageDisplayFetch causing an issue?
I'm a first-time poster so please let me know if I'm making any kind of faux pas - Super keen to learn! Any input is very welcome.
Some potentially useful code snippets:
QualityExplicitlySetMessage
public class QualityExplicitlySetMessage : QualityChangeMessage
{
public QualityExplicitlySetMessage(IQPossession before, IQPossession after, IQEffect qEffect)
: base(before, after, qEffect)
{
IsSetToExactly = true;
}
}
QualityChangeMessage - Working example
public abstract class QualityChangeMessage : CoreMessage, IQualityChangeMessage
{
protected PossessionChange Change;
public PossessionChange GetPossessionChange()
{
return Change;
}
protected QualityChangeMessage(IQPossession before, IQPossession after, IQEffect qEffect)
{
Change = new PossessionChange(before, after, qEffect);
StoreQualityInfo(qEffect.AssociatedQuality);
}
public override IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetQualityResult(this);
}
#region IQualityChangeMessage implementation
public int LevelBefore
{
get { return Change.Before.Level; }
}
//... And so on with values dependent on the Change property.
}
CoreMessage - Working example
public abstract class CoreMessage : ICoreMessage
{
public string MessageType
{
get { return GetType().ToString(); }
}
public string ImageTooltip
{
get { return _imagetooltip; }
set { _imagetooltip = value; }
}
public string Image
{
get { return _image; }
set { _image = value; }
}
public int? RelevantQualityId { get; set; }
protected void StoreQualityInfo(Quality q)
{
PyramidNumberIncreaseLimit = q.PyramidNumberIncreaseLimit;
RelevantQualityId = q.Id;
RelevantQualityName = q.Name;
ImageTooltip = "<strong>" + q.Name + "</strong><br/>" + q.Description + "<br>" +
q.EnhancementsDescription;
Image = q.Image;
}
public virtual IResultPresentation GetPresentation(IResultFormatter formatter)
{
return formatter.GetResult(this);
}
}
UserController - Working example.
public partial class UserController : Controller
{
private readonly IMessageDisplayFetch _messageDisplayFetch;
public UserController(IMessageDisplayFetch messageDisplayFetch)
{
_messageDisplayFetch = messageDisplayFetch;
}
public virtual ActionResult MessagesForStoryletWindow()
{
var activeChar = _us.CurrentCharacter();
IEnumerable<IResultPresentation> messages;
messages = _messageDisplayFetch.FlushMessagesAsPresentations(_storyFormatter);
var vd = new MessagesViewData(messages)
{
Character = new CharacterViewData(activeChar),
};
return View(Views.Messages, vd);
}
}

Categories