In my current asp.net mvc project a user should be able to log in. When logged in a set of preferences can optionally be set. Some of those preferences are general (e.g. prefered site language etc), but some are specific to this project only (pre-defined query filtering etc).
Since these preferences are present in a lot of different places of my site I would define this as cross-concern. Preferably I would have an attribute handle this rather than every action on it's own.
How can I design such an attribute that is generic enough to re-use in future projects yet knows enough about the current project to use all the project specific settings too?
--EDIT--
Getting and setting the preferences is not the problem. I connected a UserSettings class to the asp.net profile provider.
My problem is how to pull this cross concern out of my controllers into an attribute.
independently if you are storing that preferences in a text file, xml or database, wouldn't be easier to create a class (for example Utility.UserPreferences) that will load those preferences from the user and store them in a Session variable, then using an enum call them to retrieve / update
namespace Utility
{
public class UserPreferences
{
public UserPreferences(int userID)
{
// Load Preferences from text file, xml file or DB
string loadedPreferences = "us|20|yes|no";
HttpContext.Current.Session["userPreferences"] = loadedPreferences;
}
public void Savepreferences(string[] pref, int userID)
{
// Save preferences for that user
}
public static string GetPreferences(PreferencesType type)
{
string[] pref = HttpContext.Current.Session["userPreferences"].ToString().Split('|');
switch (type)
{
case PreferencesType.Language: return pref[0];
case PreferencesType.ShowHowManyResults: return pref[1];
case PreferencesType.ShowNavigation: return pref[2];
case PreferencesType.GetEmailAlerts: return pref[3];
}
}
public enum PreferencesType
{
Language, ShowHowManyResults, ShowNavigation, GetEmailAlerts
}
}
}
then ...
// Login sucessfully...
Utility.UserPreferences pref = new Utility.UserPreferences(CurrentUser.ID);
// to retrieve a preference
string language = Utility.UserPreferences.GetPreferences(
Utility.UserPreferences.PreferencesType.Language,
CurrentUser.ID);
it's just an idea... the way I would do it... it's simple and it's project(s) wide, cause you just need to change the class to hold more preferences...
For user preferences you should use the ASP.NET Profile Provider framework. Some resources:
ASP.NET Profile Providers
Implementing a Profile Provider
StackOverflow: Implementing Profile Provider in ASP.NET MVC
StackOverflow: Implementing a Custom Profile Provider in ASP.NET MVC
You could build attribute-based user preference handling on top of the Profile provider framework, but I imagine that sort of thing would be specific to your app.
Related
We are developing an nopCommerce based application. Our login page needs to be minimalistic and would need only an email id, password entry fields and a Login button.
Could you point me to best practices for achieving the above objective ?
Do I modify the corresponding pages found in \Presentation\Nop.Web\Views\Customer\ & controllers in \Presentation\Nop.Web\Controllers\
Or
Is there a better way of doing this and organizing all the modified files in one place/folder so that upgrading to future versions of nopCommerce will not be difficult ?
The requirement is to ensure that all the changes made to the project(views/controllers etc) are in one folder so that they are not overwritten when we upgrade to a newer version of nopCommerce.
I read somewhere that you can copy stuff you need to change (Login.chtml, CustomerController) to Themes/DefaultClean and then make your changes in this folder. I dont remember where i read it.
I feel doing so will make it that much easier to maintain our codebase because all your custom code is in one place/folder/sub folders
Is this a best practise? And is there a disadvantage to this method of doing things?
The best way to modify your nopCommerce project without changing anything in the core code would be to use the plugin functionality which is described here (assuming you're using the newest version 4.40).
To change the login page you would then need to create your modified version as a .cshtml file in your plugin. You then need to set this file as Content and set the Copy to Output Directory property to Copy if Newer or Copy Always.
You also need to implement the IViewLocationExpander interface so that the Razor Engine knows that it should use your custom Login Page. The implementation should look something like this:
public class MyViewLocationExpander : IViewLocationExpander
{
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
if(context.ViewName == "Login")
{
viewLocations = new[] { "PathToCustomLoginPage" }.Concat(viewLocations);
}
return viewLocations;
}
public void PopulateValues(ViewLocationExpanderContext context)
{
return;
}
}
After that you also need to register your ViewExpander by implementing the INopStartup interface. The implementation would look something like this:
public class MyStartup : INopStartup
{
public int Order => int.MaxValue;
public void Configure(IApplicationBuilder application)
{
}
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new MyViewLocationExpander());
});
}
}
Background
I've created a working bot in C# but I'm failing to expand it to be a multi-tenant bot. I have created multiple bots in the Microsoft portal using this technique to identify themselves from the messaging endpoint:
https://example.com/api/messages/bot1
https://example.com/api/messages/bot2
https://example.com/api/messages/bot3
I can grab the LastSegment from the URL while in the MessagesController and store it in PrivateConversationData so I know which bot is talking in the current conversation. I intended use this stored 'bot id' in order to retrieve the Microsoft AppId & Password from the web.config (the bot's credentials are stored as a series of custom entries and not the standard appSettings as that only works for a single bot).
Credentials Problem
The authentication works well (nearly) as described here except when using async code with .ConfigureAwait(false) I can't get the HttpContext.Current as it becomes null when running on a different thread. This means I can't get the authenticated user's credentials either by looking them up in the web.config or by calling GetCredentialsFromClaims() since I've lost the authenticated user. If I use .ConfigureAwait(true) I just get deadlocks all over the place.
I have the credentials in the web.config but they are stored per bot and I need the 'bot id' from the URL above in order to get the credentials.
Question
The crux of the problem is: I need the URL to get the 'bot id' and I need the 'bot id' to get the credentials from the web.config but I can never reliably get access to the URL once I've passed a .ConfigureAwait(false) in the code. On the flip side, I can't get the 'bot id' from the PrivateConversationData since I need the bot's credentials in order to load it. A bit chicken and egg :-(
If anyone has any ideas of what I may be doing wrong or has an alternative approach to know which 'bot id' is currently executing I'd very much appreciate it.
Thanks
Please find below given the sample code.
public class StartUp {
public void Configuration(IAppBuilder app) {
var builder = new ContainerBuilder();
//Note: Initialize / register the Metadata Service that can bring the tenant details from the corresponding store
builder.RegisterType<TenantMetadataService>().As<ITenantMetadataService>();
//Note: This helps you in accessing the TenantMetadata from any constructor going forward after the below registry
builder.Register(ti => TenantMetadata.GetTenantMetadataFromRequest()).InstancePerRequest();
//TODO: Register the various services / controllers etc which may require the tenant details here
}
}
public class TenantMetadata {
public Guid TenantId { get;set; }
public Uri TenantUrl { get;set; }
public string TenantName { get;set; }
public static TenantMetadata GetTenantMetadataFromRequest() {
var context = HttpContext.Current;
//TODO: If you have any header like TenantId coming from the request, you can read and use it
var tenantIdFromRequestHeader = "";
//TODO: There will be a lazy cache that keeps building the data as new tenant's login or use the application
if(TenantCache.Contains(...))return TenantCache[Key];
//TODO: Do a look-up from the above step and then construct the metadata
var tenantMetadata = metadataSvc.GetTenantMetadata(...);
//TODO: If the data match does not happen from the Step2, build the cache and then return the value.
TenantCache.Add(key,tenantMetadata);
return tenantMetadata;
}
}
Note
The above code snippet uses the various service placeholders, cache and the other methods which will require to be used based on the designed application services. If you wish not to cache the tenant metadata, if it may contain some sensitive data, you can remove the caching implementation parts.
This implementation can be spread across all your web facing portals like your Web UI, Web Api and WebJobs etc so that it is same across all apps and it is easy to test and consume.
HTH.
We are currently working on a smaller ASP.NET MVC 5 application using ASP.NET Identity. It allows us to maintain different projects and their tasks. We recently implemented basic authentication so we are able to register a user with our site and login with them.
We want to be able to manage access rights on project basis so we can say for every single user that he has read, write, admin or no permissions for a specified project.
My first thought was that we can create a simple new table in our database which stores the user rights. But I feel that there might be a built-in way to achieve this with ASP.NET Identity.
So my question really is, which path we should follow - manually building a new table to administer the rights or use something built-in provided by ASP.NET Identity.
use something built-in provided by ASP.NET Identity
The only things you could use there are claims or roles and both are not built for what you want IMO.
So I would go with your own table which links the project to a user, e.g.:
public class UserProjectRights
{
[Key]
public ApplicationUser User { get; set; }
[Key]
public Project Project { get; set; }
public AccessRight Right { get; set; }
}
Then whenever you do some actions where a specific right is required you need to check for that. There are several ways how you could do that. In my app I created "access right check extensions" like the following (I have defined a common interface for all "access right entities" to "reuse" that method):
public static bool? CanView(this ApplicationUser user, Project project)
{
var userRight = project.Rights.FirstOrDefault(r => r.User == user);
return userRight == null ? (bool?)null : userRight.Right.HasFlag(AccessRight.View);
}
assuming AccessRight is an enum like:
[Flags]
public enum AccessRight
{
View,
Edit,
Admin
}
Then you can do something like the following in your logic:
if (user.CanView(project) == true)
{
// show project
}
I used bool? so I can implement different "default behaviour" as I know if null is returned there is no right defined.
I have an object (ClientConfiguration) that I use on almost every page on my site, as well as in many methods that exist in related projects that get compiled into the website.
What I am doing now is creating and populating the object on each page load, and storing it in the HttpContext. This works great for anything in the UI project; and for anything in the dll projects, I pass the ClientConfiguration to any methods that may need to use it.
What I would rather do is have a "global" property that is shared among all of the related projects so I don't have to pass it around.
Is there a good way to accomplish this?
After you add System.Web.dll as reference in your other library projects, you can access the object in HttpContext directly, no need to pass as parameter.
This depends a bit on where initial configuration is being stored (xml file, database or something else) but you’ll see the point.
If these are global configuration settings that are same for all application users you can create a class like this
public class Config
{
public static ClientConfiguration Current
{
get
{
if (HttpContext.Current.Application["clientconfig"] == null)
{
//Fill object from database
}
return HttpContext.Current.Application["clientconfig"] as ClientConfiguration;
}
set
{
//store object in database
//invalidate what is stored in application object
//so that it will be refreshed next time it's used
HttpContext.Current.Application["clientconfig"] = null;
}
}
}
This will store the ClientConfiguration in global Application object and make it available in all pages so you don’t have to create it in page load.
You can just use it like this
private void Foo()
{
ClientConfiguration config = Config.Current;
}
If you have multiple projects that need to share same data then it’s best to store the object in database or in shared XML file and create new class library project so that you can just include reference to the Config class.
In a .NET application, if you have specific Settings need, such as storing them in DB, then you could replace LocalFileSettingsProvider with a custom settings provider of your, examples:
Create a Custom Settings Provider to Share Settings Between Applications
Creating a Custom Settings Provider
To declare the settings class (the one that inherits ApplicationSettingsBase) that you want to use a specific provider, you decorate it with SettingsProviderAttribute and pass your provider type as a parameter [SettingsProvider(typeof(MyCustomProvider))], otherwise it will use the default LocalFileSettingsProvider.
My question: Is there a configuration or a trick I could use to force my application to use my custom provider through-out the application without using an attribute?
The reason is that I am loading plugins via MEF and the plugins might be written via 3rd party and I don't want them to be concerned with how settings are being dealt with.
You could try the following code. It basically changes the default provider to an arbitrary one during the construction of the Settings object. Note that I never tested this code.
internal sealed partial class Settings {
public Settings() {
SettingsProvider provider = CreateAnArbitraryProviderHere();
// Try to re-use an existing provider, since we cannot have multiple providers
// with same name.
if (Providers[provider.Name] == null)
Providers.Add(provider);
else
provider = Providers[provider.Name];
// Change default provider.
foreach (SettingsProperty property in Properties)
{
if (
property.PropertyType.GetCustomAttributes(
typeof(SettingsProviderAttribute),
false
).Length == 0
)
{
property.Provider = provider;
}
}
}
}