We have some xml files in the our project and whenever we check-in these xml files into TFS, We have make sure before checking-in that we have added those xml files to proprietary application.
Now the new employees more often forget to add files into proprietary application before check-in and this is getting serious...
We want kinda confirmation dialog (a reminder) asking the developers if they have added the xml files into the app. If yes then check-in otherwise keep it checkedout...
Please suggest if such thing is possible and any relevant code or links will be really appreciated.
It's not appropriate to raise UI in a custom check-in policy - the lifecycle of a check-in policy is very short, and they will be evaluated frequently and not necessarily in a UI context or on the UI thread.
Can you determine programmatically whether the appropriate XML files are being checked in? If so, you could create a custom check-in policy that fails if the XML files are not pended for add.
Gated Check-in may be the best solution to this problem: does the build fail if these XML files do not exist - or would unit tests fail if these files are missing? If so, this is a perfect candidate for Gated Check-in, which will prevent these check-ins from occurring.
I would create a custom build template that checks for these xml files. Make it a gated check-in and you've got your solution.
The Evaluate method is supposed to be quick and should not show UI, but there is an event on the policy that triggers when the user interacts with the policy called Activate, this is a good moment to show UI and communicate with the policy. You could do something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace JesseHouwing.CheckinPolicies
{
using System.Windows.Forms;
using Microsoft.TeamFoundation.Client.Reporting;
using Microsoft.TeamFoundation.VersionControl.Client;
[Serializable]
public class ConfirmPolicy :PolicyBase
{
private AffectedTeamProjectsEventHandler _affectedTeamProjectsEventHandler;
private EventHandler _checkedPendingChangesEventHandler;
public ConfirmPolicy()
{
}
public void StatusChanged()
{
_userconfirmed = false;
OnPolicyStateChanged(Evaluate());
}
public override void Initialize(IPendingCheckin pendingCheckin)
{
_affectedTeamProjectsEventHandler = (sender, e) => StatusChanged();
_checkedPendingChangesEventHandler = (sender, e) => StatusChanged();
base.Initialize(pendingCheckin);
_userconfirmed = false;
pendingCheckin.PendingChanges.AffectedTeamProjectsChanged += _affectedTeamProjectsEventHandler;
pendingCheckin.PendingChanges.CheckedPendingChangesChanged += _checkedPendingChangesEventHandler;
}
protected override void OnPolicyStateChanged(PolicyFailure[] failures)
{
_userconfirmed = false;
base.OnPolicyStateChanged(Evaluate());
}
public override void Activate(PolicyFailure failure)
{
if (MessageBox.Show("Confirm the policy?", "Policy Check", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
_userconfirmed = true;
base.OnPolicyStateChanged(Evaluate());
}
}
public override PolicyFailure[] Evaluate()
{
if (_userconfirmed == true)
{
return new PolicyFailure[0];
}
else
{
return new PolicyFailure[]{new PolicyFailure("User must confirm", this)};
}
}
public override string Description
{
get { throw new NotImplementedException(); }
}
public override bool Edit(IPolicyEditArgs policyEditArgs)
{
return true;
}
public override string Type
{
get
{
return "User Confirm";
}
}
public override string TypeDescription
{
get
{
return "User Confirm";
}
}
public override void Dispose()
{
this.PendingCheckin.PendingChanges.AffectedTeamProjectsChanged -= _affectedTeamProjectsEventHandler;
this.PendingCheckin.PendingChanges.CheckedPendingChangesChanged -= _checkedPendingChangesEventHandler;
base.Dispose();
}
}
}
I haven't tested this exact code yet, it might need some tweaking, but this is the general thing to do. Right now it triggers on a change in the files being checked in, but you can subscribe to any of the other events as well (work item changes) or trigger your own evaluation of the project each time Evaluate is called.
Or you can just trigger the confirm once per checkin cycle. it's all up to you. You could even do a "Click to Dismiss" and skip the Messagebox altogether. Just set _userConfirmed=true and fire the PolicyStateChanged event.
Related
I need to dynamically creates controllers in a ASP.NET Core 6 MVC application.
I found some way to somewhat achieve this but not quite.
I'm able to dynamically add my controller but somehow it reflects only on the second request.
So here is what I do: first I initialize my console app as follows:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace DynamicControllerServer
{
internal class Program
{
static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
ApplicationPartManager partManager = builder.Services.AddMvc().PartManager;
// Store thePartManager in my Middleware to be able to add controlelr after initialization is done
MyMiddleware._partManager = partManager;
// Register controller change event
builder.Services.AddSingleton<IActionDescriptorChangeProvider>(MyActionDescriptorChangeProvider.Instance);
builder.Services.AddSingleton(MyActionDescriptorChangeProvider.Instance);
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
// Add Middleware which is responsible to cactn the request and dynamically add the missing controller
app.UseMiddleware<MyMiddleware>();
app.RunAsync();
Console.WriteLine("Server has been started successfully ...");
Console.ReadLine();
}
}
}
Then my middleware looks like this: it basically detects that there is the "dynamic" keyword in the url. If so, it will load the assembly containing the DynamicController:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using System;
using System.Reflection;
namespace DynamicControllerServer
{
public class MyMiddleware
{
public RequestDelegate _next { get; }
private string dllName = "DynamicController1.dll";
static public ApplicationPartManager _partManager = null;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Path.HasValue)
{
var queryParams = httpContext.Request.Path.Value;
if(httpContext.Request.Path.Value.Contains("api/dynamic"))
{
// Dynamically load assembly
Assembly assembly = assembly = Assembly.LoadFrom(#"C:\Temp\" + dllName);
// Add controller to the application
AssemblyPart _part = new AssemblyPart(assembly);
_partManager.ApplicationParts.Add(_part);
// Notify change
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
}
}
await _next(httpContext); // calling next middleware
}
}
}
The ActionDescriptorChange provider looks like this:
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives;
namespace DynamicControllerServer
{
public class MyActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
public static MyActionDescriptorChangeProvider Instance { get; } = new MyActionDescriptorChangeProvider();
public CancellationTokenSource TokenSource { get; private set; }
public bool HasChanged { get; set; }
public IChangeToken GetChangeToken()
{
TokenSource = new CancellationTokenSource();
return new CancellationChangeToken(TokenSource.Token);
}
}
}
Dynamic controller is in separate dll and is very simple:
using Microsoft.AspNetCore.Mvc;
namespace DotNotSelfHostedOwin
{
[Route("api/[controller]")]
[ApiController]
public class DynamicController : ControllerBase
{
public string[] Get()
{
return new string[] { "dynamic1", "dynamic1", DateTime.Now.ToString() };
}
}
}
Here are the packages used in that project:
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
This works "almost" fine ... when first request is made to:
https://localhost:5001/api/dynamic
then it goes in the middleware and load the assembly, but returns a 404 error.
Then second request will actually work as expected:
Second request returns the expected result:
I must doing it wrong and probably my middleware is executed too late in the flow to reflect the dynamic controller right away.
Question is: what should be the proper way to achieve this?
Second question I have is say now the external dll holding our dynamic controller is updated.
How can I reload that controller to get the new definition?
Any help would be appreciated
Thanks in advance
Nick
Here is the answer to my own question in case it can help somebody out there.
It seems building and loading the controller from the middleware will always end up with failure on the first call.
This makes sense since we are already in the http pipeline.
I end up doing same thing from outside the middleware.
Basically my application detect a change in the controller assembly, unload the original assembly and load the new one.
You cannot use the Default context since it will not allow reloading different dll for same assembly:
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); // Produce an exception on updates
To be able to reload new dll for same assembly, I’m loading each controller in its own assembly context. To do that you need to create your own class deriving from AssemblyLoadContext and managing assembly load:
public class MyOwnContext: AssemblyLoadContext
{
// You can find lots of example in the net
}
When you want to unload the assembly, you just unload the context:
MyOwnContextObj.Unload();
Now to add or remove the controller on the fly, you need to keep reference of the PartManager and the ApplicationPart.
To add controller
ApplicationPart part = new AssemblyPart(assembly);
_PartManager.ApplicationParts.Add(part);
To remove:
_PartManager.ApplicationParts.Remove(part);
On course once done, still use following piece of code to acknowledge the change:
MyActionDescriptorChangeProvider.Instance.HasChanged = true;
MyActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
That allow updating controller on the fly with no interruption of service.
Hope this helps people out there.
I have done a similar solution (used for managing a web app plugins) with some differences that may help you:
List all the external assemblies in a config file or appsettings.json so all the dll names and/or addresses are known at startup
Instead of registering controllers when they are called, register them at program.cs/start up :
//Foreah dllName from settings file
var assembly = Assembly.LoadFrom(#"Base address" + dllNameLoadedFromSettings);
var part = new AssemblyPart(assembly);
services.AddControllersWithViews()
.ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part));
// Any other configuration based on the usage you want
Second: I usually keep plugin dlls in the bin folder so when using IIS as soon as a dll file in bin is changed the upper-level app is automatically reset. So your second question would be solved too.
what is the best way to capture page views by person without slowing down performance on the site. I see that stackoverflow show page views all over the place. Are they doing an insert into a db everytime i click on a page?
In asp.net-mvc, Is there any recommended way to track page view per user (my site has a login screen) so i can review which pages people are going to and how often
First off.. if what you really care about is how are customers using my site then you most likely want to look into Google Analytics or a similar service.
But if you want a quick and dirty page view record and you are using ASP.Net MVC 3 then as Chris Fulstow mentioned you're going to want to use a mix of global action filters and caching. Here is an example.
PageViewAttribute.cs
public class PageViewAttribute : ActionFilterAttribute
{
private static readonly TimeSpan pageViewDumpToDatabaseTimeSpan = new TimeSpan(0, 0, 10);
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var calledMethod = string.Format("{0} -> {1}",
filterContext.ActionDescriptor.ControllerDescriptor.ControllerName,
filterContext.ActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = HttpRuntime.Cache[cacheKey];
if(cachedResult == null)
{
HttpRuntime.Cache.Insert(cacheKey, new PageViewValue(), null, DateTime.Now.Add(pageViewDumpToDatabaseTimeSpan) , Cache.NoSlidingExpiration, CacheItemPriority.Default,
onRemove);
}
else
{
var currentValue = (PageViewValue) cachedResult;
currentValue.Value++;
}
}
private static void onRemove(string key, object value, CacheItemRemovedReason reason)
{
if (!key.StartsWith("PV-"))
{
return;
}
// write out the value to the database
}
// Used to get around weird cache behavior with value types
public class PageViewValue
{
public PageViewValue()
{
Value = 1;
}
public int Value { get; set; }
}
}
And in your Global.asax.cs
public class MvcApplication : HttpApplication
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new PageViewAttribute());
}
}
For pre-ASP.Net MVC 3 ONLY you are going to have to apply the same attribute manually to all of your actions.
[PageView]
public ActionResult CallOne()
{
}
[PageView]
public ActionResult CallTwo()
{
}
The best way would probably be a global action filter that intercepts requests to all actions on all controllers, then increments a counter in the database for the current user and page. To save hitting the database too hard, you could cache these values and invalidate them every few minutes, depending on how much traffic you're dealing with.
We use the open source Piwik: http://piwik.org/, which is setup on it's own server. One line of Javascript in the _Layout page makes a call to Piwik after the page has loaded (put the JS at the end) and does not affect page load performance at all.
In addition to just counts, you'll get a ton of info about where your users are coming from, browser, screen resolutions, installed plugins. Plus you can track conversions and use the same tool to track marketing campaigns, etc.
<soapbox>
I cannot think of a situation where you'd be better off implementing this in MVC or in your web app in general. This stuff simply does not belong in your web app and is a meta-concern that should be separated out. This approach has enabled us to track analytics for all of our apps (32 of them: mvc 2/3, webforms, php...) in a unified manner.
If you really don't want to use another tool for this purpose, I would recommend tapping into your IIS log and getting your stats from there. Again, to get any real decision making power out of it, you'll need to put a good analyzer on it. I recommend Splunk: http://www.splunk.com/
</soapbox>
I wanted to post an updated version of Shane's answer for those who are interested. Some things to consider:
You have to set the action attribute up as a service when decorating your
methods using syntax like the following :
[ServiceFilter(typeof(PageViewAttribute))]
As far as I can tell, HttpRuntime.Cache.Insert isn't a thing in .NET Core, so I used a simple implementation of IMemoryCache (You may need to add this line to your startup.cs in order to use the interface):
services.AddMemoryCache();
Because we are injecting IMemoryCache into a class that is not a controller, we need to register our attribute as a service in startup.cs, like so:
services.AddScoped<[PageViewAttribute]>(); - without brackets!
Whatever object you return when creating a cacheKey will be assigned to the 'value' parameter of the OnRemove method.
Below is the code.
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controllerActionDescriptor = filterContext.ActionDescriptor as ControllerActionDescriptor;
var arguments = filterContext.ActionArguments;
ActionId = arguments["id"].ToString();
var calledMethod = string.Format("{0} -> {1}",
controllerActionDescriptor.ControllerName,
controllerActionDescriptor.ActionName);
var cacheKey = string.Format("PV-{0}", calledMethod);
var cachedResult = _memoryCache.Get(cacheKey);
if (cachedResult == null)
{
//Get cacheKey if found, if not create cache key with following settings
_memoryCache.GetOrCreate(cacheKey, cacheKey =>
{
cacheKey.AbsoluteExpirationRelativeToNow
= pageViewDumpToDatabaseTimeSpan;
cacheKey.SetValue(1);
cacheKey.RegisterPostEvictionCallback(onRemove);
return cacheKey.Value;
});
}
else
{
_memoryCache.Get(cacheKey);
}
}
//Called when Memory entry is removed
private void onRemove(object key, object value, EvictionReason reason, object state)
{
if (!key.ToString().StartsWith("PV-"))
{
return;
}
// write out the value to the database
SaveToDataBase(key.ToString(), (int)value);
}
As a point of reference, this was done for a .NET Core 5 MVC App.
Regards.
I'll start by saying i'm a C# MVC newbie, but I've set up a site with Identity Management and extended the database with some custom tables to store additional info about my users, so I'm not a total neophyte. I've been working on a VB WPF application that I want to deploy from my new website and that is where I'm running into an issue.
I've created a new controller (User) and a couple of views (Download) & (Setup). I created a downloadmodel used by the download view.
In abstract what I am doing is displaying the download view (get) which has three checkboxes to confirm the user has read the Overview, Installation, and Terms of Service. These are boolean values in the model. I also have a string response message in the model, that displays just above the submit button. Here is the model:
public class DownloadModel
{
public bool Overview { get; set; }
public bool Installation { get; set; }
public bool TermsOfService { get; set; }
public string Response { get; set; }
public DownloadModel()
{
Overview = false;
Installation = false;
TermsOfService = false;
Response = "After checking boxes click the button to begin installation";
}
}
My User Controller handles the Get to initially display the download view, and then in the Post it checks to see if all the checkboxes were ticked, if not it updates the response message and returns the view.
If the checkboxes are all checked then it pulls the subscriber (which must exist because it was created when the user verified their e-mail via the account controller - identity management), then proceeds to update the subscriber with the original (if new) or last download date(s). At this point I want to begin downloading the clickonce setup.exe file, before returning the setup view.
[Authorize]
public class UserController : Controller
{
// GET: User/Download
public ActionResult Download()
{
return View(new DownloadModel { });
}
// Post: User/Download
[HttpPost]
public ActionResult Download(DownloadModel downloadcheck)
{
if (!ModelState.IsValid)
{
return View(downloadcheck);
}
//check to see if all the boxes were checked
if (downloadcheck.Overview == true &
downloadcheck.Installation == true &
downloadcheck.TermsOfService == true)
{
//yes - so let's proceed
//first step is to get the subscriber
Subscriber tSubscriber = new Subscriber();
tSubscriber.Email = User.Identity.Name;
bool okLoad = tSubscriber.LoadByEmail();
if (okLoad == false)
{
//we have a real problem. a user has logged in but they are not yet
//a valid subscriber?
throw new Exception("Subscriber not found");
}
// update subscriber with download in process...
if (tSubscriber.OriginalDownload == DateTime.MinValue)
{
tSubscriber.OriginalDownload = DateTime.Now;
tSubscriber.LastDownload = tSubscriber.OriginalDownload;
}
else
{
tSubscriber.LastDownload = DateTime.Now;
}
if (tSubscriber.UpdateDownloaded() == false)
{
//update of download dates failed
//another problem that shouldnt occur.
downloadcheck.Response = "A problem occured downloading your setup."
+ "Try again. If this error continues please contact support.";
return View(downloadcheck);
}
//download dates have been updated for the subscriber so let's start the download!
//THIS IS WHERE I NEED TO BEGIN THE DOWNLOAD
return View("Setup");
}
else
{
// all boxes were not checked - update message
downloadcheck.Response = "Please confirm you have reviewed the above information "
+ "by checking all of the boxes before clicking on the button.";
return View(downloadcheck);
}
}
}
The download view is pretty straight forward, and the setup view simply confirms the download was started and provides a link to the help-setup page.
I'm really a bit lost here. I thought I'd plug in a return new filepathresponse, but I can't do that and return the setup view.
My other thought was to somehow trigger the download of my /xxx/setup.exe from within the setup view as it is returned - but I'm at a loss as to how to accomplish this.
I'll be the first to admit that my mvc c# code is probably overly verbose and my approach to how I've done this may be totally wrong, but I'm just scrambling to get this done so I can deploy my WPF app to select Beta users for testing. It's been a long time living off savings and I can smell go-live from here.
One final note, I'm using setup.exe clickonce deployment of my wpf app for simplicity, as there are .net and localsqldb prerequisites, but I will not be using automated updates - not that this is really relevant.
Appreciate all input and advice.
After more digging and hacking I've found a solution that works. Firstly in my setup view (confirmation page) I added a simple script to initiate a new function in my user controller:
<script type="text/javascript">
window.location.href = "/user/sendfile/"
</script>
The controller change was simple too. For testing I just used a txt file.
// User/SendFile
public ActionResult SendFile()
{
string path = #"~/xxxx/anyfile.txt";
string content = "application/txt";
//string content = "application/x-ms-application";
return new FilePathResult(path, content)
{
FileDownloadName = "mynewtext.txt"
};
}
What is really interesting about this solution is that the FileDownloadName is what the file content is downloaded as. So in this way I can refer to the actual setup.exe file in the path, but then name the downloaded exe anything I want. Bonus :)
I am writing an Excel 2007 Addin. using VS2008 and .net 3.5, C#.
I catched Microsoft.Office.Interop.Excel.Application's WindowActivate and WindowDeActivate events.
It was surprised to know that WindowActivate and Deactivate only triggers when i switch between two Excel Windows. if i switch to notepad, i expect Deactivate to be triggered, but its not happening. same way from notepad if i switch to excel window, i expect Activate to be triggered but its not happening. It looks like the behaviour indicates windows are MDI-Child windows.
Now what i want to do is get HWnd of Excel's Mainwindow and hook Window Activate and Deactivates using dllimport features.
Can anyone guide to me on this.
Regards
I solved similar problem when writing Excel addin. No dll import is needed. I solved this issue using System.Windows.Forms.NativeWindow class.
At first, I made my own class inherited from NativeWindow class and declared two events Activated and Deactivate in it and finaly overrided WndProc() method to rise these events when message WM_ACTIVATE is passed to the WndProc method. According to "Message" parameter WParm is Excel window activated or deactivated.
public class ExcelWindow: NativeWindow
{
public const int WM_ACTIVATED = 0x0006;
public ExcelWindow():base(){}
//events
public event EventHandler Activated;
public event EventHandler Deactivate;
//catching windows messages
protected override void WndProc(ref Message m)
{
if (m.Msg== WM_ACTIVATED)
{
if (m.WParam.ToInt32() == 1)
{
//raise activated event
if (Activated!=null)
{
Activated(this, new EventArgs());
}
}
else if (m.WParam.ToInt32() == 0)
{
//raise deactivated event
if (Deactivate!=null)
{
Deactivate(this, new EventArgs());
}
}
}
base.WndProc(ref m);
}
}
Then I made in my addin class field "ExcelWindow myExcelWindow" and added following code to OnConnection method of my addin:
ExcelWindow myExcelWindow;
void Extensibility.IDTExtensibility2.OnConnection(object application, Extensibility.ext_ConnectMode ConnectMode, object AddInInst, ref Array custom)
{
excel = application as Excel.Application;
myExcelWindow = new ExcelWindow();
myExcelWindow.AssignHandle(new IntPtr(excel.Hwnd));
myExcelWindow.Activated += new EventHandler(myExcelWindow_Activated);
myExcelWindow.Deactivate += new EventHandler(myExcelWindow_Deactivate);
//addin code here
}
void myExcelWindow_Activated(object sender, EventArgs e)
{
//do some stuff here
}
void myExcelWindow_Deactivate(object sender, EventArgs e)
{
//do some stuff here
}
I hope this will help you.
Finally I found one solution..that works only Activate/Deactivate.
This is not the perfect way to do it. But I did not find any good alternative.
This method uses polling. I have to call following function in each 10 ms interval to check focus in/out.
public static bool ApplicationIsActivated()
{
var activatedHandle = GetForegroundWindow();
if (activatedHandle == IntPtr.Zero)
{
return false; // No window is currently activated
}
var procId = Process.GetCurrentProcess().Id;
int activeProcId;
GetWindowThreadProcessId(activatedHandle, out activeProcId);
return activeProcId == procId;
}
I have an ASP.NET MVC app which depends on a lot of settings (name-value pairs), I am planning to store this information in a database table called SiteSettings. Is there an easy way in which I can get these settings using NHibernate. And what are the best practices when saving settings for a web application. And by settings I mean the settings which control the flow of processes in the web application and which are governed by business rules. These are not the typical connection string kind of settings. I was unable to get much information on the web on this topic. Maybe I am not searching on the right keywords, Any help will be greatly appreciated.
I can't answer in the context of nhibernate (which I'm not using) or best practices (I came up with this on my own recently). However, it works well for me, and will probably work for you.
I have a table (Biz_Config) in the database to store business preferences. (I've created a web.config section for what I call IT preferences.)
I have a class that is in charge of managing the biz preferences. The constructor grabs the entire table (one row per setting) and copies these into a dictionary, and it has methods to access (such as bizconfig.get("key")) and update this dictionary, also updating the table at the same time. It also has a few shortcut properties for specific dictionary values, especially where the value has to be cast (I have a few important numbers). It works quite well.
In order to be more efficient and not instantiate it every time I need a setting, and also to access it easily from my controllers and views, I created a static class, Globals, that is in charge of getting things out of the session or application variables. For the biz config object, it checks the application variable and, if null, creates a new one. Otherwise it just returns it. Globals is part of my helpers namespace, which is included in my web.config to be available to my views. So I can easily call:
<% Globals.Biz_Config.Get("key") %>
I hope this helps. If you'd like code, I can dig that up for you.
James
If you have a set of key/value pairs, you probably want to use a <map>. See the official NHibernate documentation or Ayende's post about 'NHibernate Mapping - <map/>'.
I have come up with a solution which is quite similar to the one suggested by James. I have an SiteSettingsService class which manages the settings for the whole site, it has a simple dependency on an interface called ISiteServiceRepository. This might not be the most elegant solution, But it is working perfectly for me. I have also configured the SiteSettingsService class as a Singleton using StructureMap. So, it saves me unnecessary instantiantion every time I need any settings.
//ISiteServiceRepository, an implementation of this uses NHibernate to do just two things
//i)Get all the settings, ii)Persist all the settings
using System.Collections.Generic;
using Cosmicvent.Mcwa.Core.Domain.Model;
namespace Cosmicvent.Mcwa.Core.Domain {
public interface ISiteServiceRepository {
IList<Setting> GetSettings();
void PersistSettings(IDictionary<string, string> settings);
}
}
//The main SiteSettingsService class depends on the ISiteServiceRepository
using System;
using System.Collections.Generic;
using Cosmicvent.Mcwa.Core.Domain;
using Cosmicvent.Mcwa.Core.Domain.Model;
namespace Cosmicvent.Mcwa.Core.Services {
public class SiteSettingsService : ISiteSettingsService {
private readonly ISiteServiceRepository _siteServiceRepository;
private IDictionary<string, string> _settings;
public SiteSettingsService(ISiteServiceRepository siteServiceRepository) {
_siteServiceRepository = siteServiceRepository;
//Fill up the settings
HydrateSettings();
}
public int ActiveDegreeId {
get {
return int.Parse(GetValue("Active_Degree_Id"));
}
}
public string SiteTitle {
get { return GetValue("Site_Title"); }
}
public decimal CounsellingFee {
get { return decimal.Parse(GetValue("Counselling_Fee")); }
}
public decimal TuitionFee {
get { return decimal.Parse(GetValue("Tuition_Fee")); }
}
public decimal RegistrationFee {
get { return decimal.Parse(GetValue("Registration_Fee")); }
}
public void UpdateSetting(string setting, string value) {
if (!string.IsNullOrEmpty(setting) && !string.IsNullOrEmpty(value)) {
SetValue(setting, value);
PersistSettings();
}
}
//Helper methods
private void HydrateSettings() {
_settings = new Dictionary<string, string>();
IList<Setting> siteRepoSettings = _siteServiceRepository.GetSettings();
if (siteRepoSettings == null) {
throw new ArgumentException("Site Settings Repository returned a null dictionary");
}
foreach (Setting setting in siteRepoSettings) {
_settings.Add(setting.Name.ToUpper(), setting.Value);
}
}
private string GetValue(string key) {
key = key.ToUpper();
if (_settings == null) {
throw new NullReferenceException("The Site Settings object is Null");
}
if (!_settings.ContainsKey(key)) {
throw new KeyNotFoundException(string.Format("The site setting {0} was not found", key));
}
return _settings[key];
}
private void SetValue(string key, string value) {
key = key.ToUpper();
if (_settings == null) {
throw new NullReferenceException("The Site Settings object is Null");
}
if (!_settings.ContainsKey(key)) {
throw new KeyNotFoundException(string.Format("The site setting {0} was not found", key));
}
_settings[key] = value;
}
private void PersistSettings() {
_siteServiceRepository.PersistSettings(_settings);
}
}
}
Hope this helps future developers facing similar problems. Any suggestions for improving this are more than welcome.