I'm using a IStringLoclizer approach with a resx file containing key-value pairs to localize my app as decribed in the documentation.
I've just discovered that:
On the one hand the .resx file is case insensitive (storing the key Firstname and FirstName will throw an error of key already existing).
On the second hand, when I want to RETRIEVE the keys, it IS case sensitive (if the key Firstname exists and I want to get the value of FirstName, the value is not retrieved by IStringLoclizer - which makes sense from the key-value pair point of view!).
Is there a way to override the IStringLocalizer getters, in order to implement some logic (e.g. change all keys to lowercase, and search any key by lowercase)? The key of a valid solution is to avoid changing all keys in the .resx file AND wherever I call the IStringLocalizer.
Edit
I found out about ResourceManager.IgnoreCase here, but it is not clear to me how to access the resource manager - probably this has to be done in Startup.cs somehow?
This can be achieved by overwriting the ResourceManagerStringLocalizerFactory.
public class CaseInsensitiveResourceManagerStringLocalizerFactory : ResourceManagerStringLocalizerFactory
{
public CaseInsensitiveResourceManagerStringLocalizerFactory(IOptions<LocalizationOptions> localizationOptions, ILoggerFactory loggerFactory) : base(localizationOptions, loggerFactory)
{
}
//unfortunately we need to use reflection to the the ResourceManager as the field is private
private readonly FieldInfo _field = typeof(ResourceManagerStringLocalizer).GetField("_resourceManager", BindingFlags.NonPublic | BindingFlags.Instance);
//override this method to access the resource manager at the time it is created
protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName)
{
//call the base method to get the localizer, I would like to override this implementation but the fields used in the construction are private
var localizer = base.CreateResourceManagerStringLocalizer(assembly, baseName);
if (_field == null) return localizer;
//set the resource manager to ignore case
if (_field.GetValue(localizer) is ResourceManager resourceManager) resourceManager.IgnoreCase = true;
return localizer;
}
}
The you need to register the factory before you add the localization.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IStringLocalizerFactory, CaseInsensitiveResourceManagerStringLocalizerFactory>();
services.AddLocalization(options => { options.ResourcesPath = "Resources"; });
//other service configurations....
}
I hope in the future these class are made more extensible. Private fields in both in ResourceManagerStringLocalizerFactory and ResourceManagerStringLocalizer necessitates reflection.
Related
I have a project consisting of 2 parts:
ASP.NET API using Entity Framework
.NET MAUI Client App
I use DTOs for comunication from/to the API in order not to expose other properties of my entities. Thanks to this approach I was able to separate Entity data and data that are sent from the API.
At first I used these DTOs also in the MAUI UI. But after some time I started to notice that they contains UI-specific properties, attributes or methods that have no purpose for the API itself, so they are redundant in requests.
EXAMPLE:
1 - API will receive request from MAUI to get exercise based on it's name
2- ExerciseService returns: ExerciseEntity and ExerciseController use AutoMapper to Map ExerciseEntity -> ExerciseDto ommiting ExerciseId field (only admin can see this info in the DB) and returning it in the API response
3 - MAUI receives from the API ExerciseDto. But in the client side it also want to know if data from ExerciseDto are collapsed in the UI. So because of that I add IsCollapsed property into the ExerciseDto. But now this is a redundant property for the API, because I dont want to persist this information in the database.
QUESTIONS:
Should I map these DTOs to new objects on the client side ?
Or how to approach this problem ?
Is there an easier way how to achieve the separation ?
Because having another mapping layer will add extra complexity and a lot of duplicate properties between DTOs and those new client objects.
Normally if you use clean architecture approach your DTOs shoud contain no attributes and other specific data relevant just for some of your projects, to be freely usable by other projects in a form of dependency.
Then you'd have different approaches to consume DTOs in a xamarin/maui application, for example:
APPROACH 1.
Mapping (of course) into a class that is suitable for UI. Here you have some options, use manual mapping, write your own code that uses reflection or use some third party lib using same reflection. Personally using all of them, and when speaking of third party libs Mapster has shown very good to me for api and mobile clients.
APPROACH 2.
Subclass DTO. The basic idea is to deserialize dto into the derived class, then call Init(); if needed. All properties that you manually implemented as new with OnPropertyChanged will update bindings after being popupated by deserializer/mapper and you alse have a backup plan to call RaiseProperties(); for all of the props, even thoses who do not have OnPropertyChanged in place so they can update bindings if any.
Example:
our Api DTO
public class SomeDeviceDTO
{
public int Id { get; set; }
public int Port { get; set; }
public string Name { get; set; }
}
Our derived class for usage in mobile client:
public class SomeDevice : SomeDeviceDTO, IFromDto
{
// we want to be able to change this Name property in run-time and to
// reflect changes so we make it bindable (other props will remain without
// OnPropertyChanged BUT we can always update all bindings in code if needed
// using RaiseProperties();):
private string _name;
public new string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
// ADD any properties you need for UI
// ...
#region IFromDto
public void Init()
{
//put any code you'd want to exec after dto's been imported
// for example to fill any new prop with data derived from what you received
}
public void RaiseProperties()
{
var props = this.GetType().GetProperties();
foreach (var property in props)
{
if (property.CanRead)
{
OnPropertyChanged(property.Name);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public interface IFromDto : INotifyPropertyChanged
{
//
// Summary:
// Can initialize model after it's being loaded from dto
void Init();
//
// Summary:
// Notify all properties were updated
void RaiseProperties();
}
We can get it like: var device = JsonConvert.DeserializeObject<SomeDevice>(jsonOfSomeDeviceDTO);
We then can call Init(); if needed..
Feel free to edit this answer to add more approaches..
I'm looking into asp.net core and the new security policies and claims functionality. Having just looked at it I don't see how it is much better than the existing authorize attribute logic in the past where hard-coded roles or users are decorated on controllers, methods etc. To me the issues has just been moved from hard-coding in attributes to hard-coding policies.
Ideally I would like to perform activity/resource based authorization where everything would be database driven. Each activity or resource would be stored in the database and a permission/role would be assigned to the resource.
While researching the topic I found this fantastic article by Stefan Wloch that pretty much covers exactly what I'm looking to do.
http://www.codeproject.com/Articles/1079552/Custom-Roles-Based-Access-Control-RBAC-in-ASP-NE
So my question is with the new core features how does it prevent us from having to hard-code and recompile when the time comes to change what roles/permissions are allowed to access a controller or method in a controller? I understand how claims can be used to store anything but the policy portion seems susceptible to change, which gets us back to square one. Don't get me wrong, loving asp.net core and all the great changes, just looking for more information on how to handle authorization.
There are at least 2 things that need to be consider in implementing what you want. The first one is how to model the Controller-Action access in database, the second one is to apply that setting in asp.net core Identity.
The first one, there are too many possibilities depend on the application itself, so lets create a Service interface named IActivityAccessService that encapsulate. We use that service via dependency injection so that anything that we need can be injected to it.
As for the second one, it can be achieved by customize AuthorizationHandler in a policy-based authorization. The first step is to setup things in Startup.ConfigureServices :
services.AddAuthorization(options =>
{
options.AddPolicy("ActivityAccess", policy => policy.Requirements.Add( new ActivityAccessRequirement() ));
});
services.AddScoped<IAuthorizationHandler, ActivityAccessHandler>();
//inject the service also
services.AddScoped<IActivityAccessService, ActivityAccessService>();
//code below will be explained later
services.AddHttpContextAccessor();
next we create the ActivityAccessHandler:
public class ActivityAccessHandler : AuthorizationHandler<ActivityAccessRequirement>
{
readonly IActivityAccessService _ActivityAccessService;
public ActivityAccessHandler (IActivityAccessService r)
{
_ActivityAccessService = r;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext authHandlerContext, ActivityAccessRequirement requirement)
{
if (context.Resource is AuthorizationFilterContext filterContext)
{
var area = (filterContext.RouteData.Values["area"] as string)?.ToLower();
var controller = (filterContext.RouteData.Values["controller"] as string)?.ToLower();
var action = (filterContext.RouteData.Values["action"] as string)?.ToLower();
var id = (filterContext.RouteData.Values["id"] as string)?.ToLower();
if (_ActivityAccessService.IsAuthorize(area, controller, action, id))
{
context.Succeed(requirement);
}
}
}
}
public class ActivityAccessRequirement : IAuthorizationRequirement
{
//since we handle the authorization in our service, we can leave this empty
}
Since we can use dependency injection in AuthorizationHandler, it is here that we inject the IActivityAccessService.
Now that we have access to what resource is being requested, we need to know who is requesting it. This can be done by injecting IHttpContextAccessor. Thus services.AddHttpContextAccessor() is added in code above, it is for this reason.
And for the IActivityAccessService, you could do something like:
public class ActivityAccessService : IActivityAccessService
{
readonly AppDbContext _context;
readonly IConfiguration _config;
readonly IHttpContextAccessor _accessor;
readonly UserManager<AppUser> _userManager;
public class ActivityAccessService(AppDbContext d, IConfiguration c, IHttpContextAccessor a, UserManager<AppUser> u)
{
_context = d;
_config = c;
_accessor = a;
_userManager = u;
}
public bool IsAuthorize(string area, string controller, string action, string id)
{
//get the user object from the ClaimPrincipals
var appUser = await _userManager.GetUserAsync(_accessor.HttpContext.User);
//get user roles if necessary
var userRoles = await _userManager.GetRolesAsync(appUser);
// all of needed data are available now, do the logic of authorization
return result;
}
}
Please note that the code in IsAuthorize body above is an example. While it will works, people might say it's not a good practice. But since IActivityAccessService is just a common simple service class, we can inject anything that wee need to it and modify the IsAuthorize method signature in any way that we want to. For example, we can just pass the filterContext.RouteData instead.
As for how to apply this to a controller or action:
[Authorize(Policy = "ActivityAccess")]
public ActionResult<IActionResult> GetResource(int resourceId)
{
return Resource;
}
hope this helps
I'm trying to decide if the custom Authorization attribute I wrote is really a good idea.
Scenario
Say we have a collection of stores, each Store has an owner. Only the owner of the store can do CRUD operations on the store. EXCEPT for users with a Claim that basically overrides the ownership requirement and says they can do CRUD operations on ANY store.
Sidenote: I'm using Thinktecture and ADFS
So I made a StoreOwnerAuthorize attribute who's parameters ("Manage", "Stores") are used to check if the user has the appropriate claim to "override" not being an owner but still able to pass the authorization check.
I'm not sure how I feel about having a claim like "ManageStores" and making the database call inside the attribute. It makes me think I'm going down the wrong road, even though it does accomplish exactly what I need.
API Routes
api/v1/Store/{storeId:int:min(1)}/employees
api/v1/Store/{storeId:int:min(1)}/inventory
API Method
[StoreOwnerAuthorize("Manage", "Stores")]
[ResourceAuthorize("View", "Store")]
[Route("")]
//api/v1/Store/{storeId:int:min(1)}/employees
public IHttpActionResult GetStoreEmployees(int storeId)
{
return Ok(collectionOfStoreEmployees);
}
StoreOwnerAuthorizeAttribute
public class StoreOwnerAuthorizeAttribute : ResourceAuthorizeAttribute
{
private readonly DbContext _context = new DbContext();
public StoreOwnerAuthorizeAttribute(){ }
public StoreOwnerAuthorizeAttribute(string action, params string[] resources)
: base(action, resources) { }
protected override bool IsAuthorized(HttpActionContext actionContext)
{
//If the user has the Claim that overrides the requirement that the user
//is the Owner of the Store, skip checking if they are the owner
if (base.IsAuthorized(actionContext))
return true;
//Get route parameter to lookup Store and determine if the user is the owner
object storeId;
actionContext.ControllerContext.RouteData.Values.TryGetValue("storeId", out storeId);
var isOwner = false;
if (storeId != null)
{
isOwner =
_context.Store_Get_ByStoreID(int.Parse(storeId.ToString()))
.Any(x => x.OwnerId == theUser.Id());
}
return isOwner;
}
}
Currently, I am using a Dictionary and Context.User.Identity.Name (code condensed for brevity):
[Authorize]
public class ServiceHub : Hub
{
static private Dictionary<string, HubUserProcess> UserProcesses = new Dictionary<string, HubUserProcess>();
public override Task OnConnected()
{
UserProcesses[Context.User.Identity.Name] = new HubUserProcess();
return base.OnConnected();
}
public override Task OnDisconnected()
{
// ... Remove from dictionary if key exists (not shown) ...
return base.OnConnected();
}
// Then I use UserProcesses[Context.User.Identity.Name] in all functions
}
In my HubUserProcess class, I have a bunch of web services that initialize in the constructor using the Context.User.Identity.Name. A coworker said that my approach is unsafe, so my biggest worry is one user accessing another user's private data (these variables can hold very sensitive information). What is the correct/safe way to store client variables?
I want to write a unit test that verifies my route registration and ControllerFactory so that given a specific URL, a specific controller will be created. Something like this:
Assert.UrlMapsToController("~/Home/Index",typeof(HomeController));
I've modified code taken from the book "Pro ASP.NET MVC 3 Framework", and it seems it would be perfect except that the ControllerFactory.CreateController() call throws an InvalidOperationException and says This method cannot be called during the application's pre-start initialization stage.
So then I downloaded the MVC source code and debugged into it, looking for the source of the problem. It originates from the ControllerFactory looking for all referenced assemblies - so that it can locate potential controllers. Somewhere in the CreateController call-stack, the specific trouble-maker call is this:
internal sealed class BuildManagerWrapper : IBuildManager {
//...
ICollection IBuildManager.GetReferencedAssemblies() {
// This bails with InvalidOperationException with the message
// "This method cannot be called during the application's pre-start
// initialization stage."
return BuildManager.GetReferencedAssemblies();
}
//...
}
I found a SO commentary on this. I still wonder if there is something that can be manually initialized to make the above code happy. Anyone?
But in the absence of that...I can't help notice that the invocation comes from an implementation of IBuildManager. I explored the possibility of injecting my own IBuildManager, but I ran into the following problems:
IBuildManager is marked internal, so I need some other authorized derivation from it. It turns out that the assembly System.Web.Mvc.Test has a class called MockBuildManager, designed for test scenarios, which is perfect!!! This leads to the second problem.
The MVC distributable, near as I can tell, does not come with the System.Web.Mvc.Test assembly (DOH!).
Even if the MVC distributable did come with the System.Web.Mvc.Test assembly, having an instance of MockBuildManager is only half the solution. It is also necessary to feed that instance into the DefaultControllerFactory. Unfortunately the property setter to accomplish this is also marked internal (DOH!).
In short, unless I find another way to "initialize" the MVC framework, my options now are to either:
COMPLETELY duplicate the source code for DefaultControllerFactory and its dependencies, so that I can bypass the original GetReferencedAssemblies() issue. (ugh!)
COMPLETELY replace the MVC distributable with my own build of MVC, based on the MVC source code - with just a couple internal modifiers removed. (double ugh!)
Incidentally, I know that the MvcContrib "TestHelper" has the appearance of accomplishing my goal, but I think it is merely using reflection to find the controller - rather than using the actual IControllerFactory to retrieve a controller type / instance.
A big reason why I want this test capability is that I have made a custom controller factory, based on DefaultControllerFactory, whose behavior I want to verify.
I'm not quite sure what you're trying to accomplish here. If it's just testing your route setup; you're way better off just testing THAT instead of hacking your way into internals. 1st rule of TDD: only test the code you wrote (and in this case that's the routing setup, not the actual route resolving technique done by MVC).
There are tons of posts/blogs about testing a route setup (just google for 'mvc test route'). It all comes down to mocking a request in a httpcontext and calling GetRouteData.
If you really need some ninja skills to mock the buildmanager: there's a way around internal interfaces, which I use for (LinqPad) experimental tests. Most .net assemblies nowadays have the InternalsVisibleToAttribute set, most likely pointing to another signed test assembly. By scanning the target assembly for this attribute and creating an assembly on the fly that matches the name (and the public key token) you can easily access internals.
Mind you that I personally would not use this technique in production test code; but it's a nice way to isolate some complex ideas.
void Main()
{
var bm = BuildManagerMockBase.CreateMock<MyBuildManager>();
bm.FileExists("IsCool?").Dump();
}
public class MyBuildManager : BuildManagerMockBase
{
public override bool FileExists(string virtualPath) { return true; }
}
public abstract class BuildManagerMockBase
{
public static T CreateMock<T>()
where T : BuildManagerMockBase
{
// Locate the mvc assembly
Assembly mvcAssembly = Assembly.GetAssembly(typeof(Controller));
// Get the type of the buildmanager interface
var buildManagerInterface = mvcAssembly.GetType("System.Web.Mvc.IBuildManager",true);
// Locate the "internals visible to" attribute and create a public key token that matches the one specified.
var internalsVisisbleTo = mvcAssembly.GetCustomAttributes(typeof (InternalsVisibleToAttribute), true).FirstOrDefault() as InternalsVisibleToAttribute;
var publicKeyString = internalsVisisbleTo.AssemblyName.Split("=".ToCharArray())[1];
var publicKey = ToBytes(publicKeyString);
// Create a fake System.Web.Mvc.Test assembly with the public key token set
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "System.Web.Mvc.Test";
assemblyName.SetPublicKey(publicKey);
// Get the domain of our current thread to host the new fake assembly
var domain = Thread.GetDomain();
var assemblyBuilder = domain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
moduleBuilder = assemblyBuilder.DefineDynamicModule("System.Web.Mvc.Test", "System.Web.Mvc.Test.dll");
AppDomain currentDom = domain;
currentDom.TypeResolve += ResolveEvent;
// Create a new type that inherits from the provided generic and implements the IBuildManager interface
var typeBuilder = moduleBuilder.DefineType("Cheat", TypeAttributes.NotPublic | TypeAttributes.Class, typeof(T), new Type[] { buildManagerInterface });
Type cheatType = typeBuilder.CreateType();
// Magic!
var ret = Activator.CreateInstance(cheatType) as T;
return ret;
}
private static byte[] ToBytes(string str)
{
List<Byte> bytes = new List<Byte>();
while(str.Length > 0)
{
var bstr = str.Substring(0, 2);
bytes.Add(Convert.ToByte(bstr, 16));
str = str.Substring(2);
}
return bytes.ToArray();
}
private static ModuleBuilder moduleBuilder;
private static Assembly ResolveEvent(Object sender, ResolveEventArgs args)
{
return moduleBuilder.Assembly;
}
public virtual bool FileExists(string virtualPath) { throw new NotImplementedException(); }
public virtual Type GetCompiledType(string virtualPath) { throw new NotImplementedException(); }
public virtual ICollection GetReferencedAssemblies() { throw new NotImplementedException(); }
public virtual Stream ReadCachedFile(string fileName) { throw new NotImplementedException(); }
public virtual Stream CreateCachedFile(string fileName) { throw new NotImplementedException(); }
}