How can I conditionally map properties using Automapper based on user permissions - automapper

I am using Automapper successfully throughout my application however I have come across a particular requirement that I cannot find a solution for.
My application is an ASP.Net Core 3.1 application using Automapper 10. What I need to do is only map some properties from the Dto to the data entity if a user has permission to edit these specific properties. I am using a Claims Principal to store the information I need regarding the user.
In the simplest form I only want to map the property "Enabled" if the user performing the editing is an administrator. I can check if the user is an admin based on claims but I have no idea how I let Automapper know this.
Can it be done or am I better manually map in this instance?
Thanks in advance

One way is to store the user role information in the Items dictionary on runtime. Then using Condition() you can check the role during mapping by retrieving the user role information from the same Items dictionary.
Mapping usage inside a controller:
var entity = mapper.Map<Entity>(dto, opts =>
{
opts.Items["UserRole"] = User.FindFirst(ClaimTypes.Role)?.Value;
});
Mapping configuration:
CreateMap<DTO, Entity>()
.ForMember(
d => d.Enabled,
o =>
{
o.Condition((s, d, sm, dt, resolutionContext) =>
{
return resolutionContext.Items.TryGetValue("UserRole", out var userRole)
&& userRole.ToString() == "Admin";
});
o.MapFrom(s => s.Enabled);
});
Other way is to use ServiceCtor from options inside a ResolutionContext to resolve any services required to establish user role, I will use IHttpContextAccessor as an example:
Mapping usage does not change at all:
var entity = mapper.Map<Entity>(dto);
Mapping configuration a bit more complex:
CreateMap<DTO, Entity>()
.ForMember(
d => d.Enabled,
o =>
{
o.Condition((s, d, sm, dt, resolutionContext) =>
{
var httpContextAccessor = resolutionContext
.Options
.ServiceCtor(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
var userRole = httpContextAccessor
.HttpContext
.User
.FindFirst(ClaimTypes.Role)
?.Value;
return userRole == "Admin";
});
o.MapFrom(s => s.Enabled);
});
Keep in mind that IHttpContextAccessor does not always return a HttpContext. It may be null. It also requires that you register it in ConfigureServices() in your Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}

Related

CS1646 Keyword, identifier, or string expected after verbatim specifier: #

tables in my EntityFramework model are events, eventtypes, subevents, subeventtypes
using the MVC5 builders (right click on controllers, add, add controller) I created controllers and views for the last three tables without issue however when I create the controller and views for the events entity I produce the following errors
Keyword, identifier, or string expected after verbatim specifier: #
'EventType' is a type, which is not valid in the given context
the code that was generated in the event controller is
{
private Entities db = new Entities();
// GET: Events
public ActionResult Index()
{
var events = db.Events.Include(# => #.EventType); ERROR HERE
return View(events.ToList());
}
any help with this issue would be greatly appreciated
TIA
I experienced the same issue when using the "MVC Controller with views, using Entity Framework" template.
var #group = await _context.Groups
.Include(# => #.Company)
.FirstOrDefaultAsync(m => m.GroupId == id);
My workaround was simple to replace the # symbol with another character i.e. g
var #group = await _context.Groups
.Include(g => g.Company)
.FirstOrDefaultAsync(m => m.GroupId == id);

App Domain Unloaded while debugging plugin

I am creating a plugin on Disassociate message of Project (custom entity); having N:N relationship with User entity. It's not working. Here is the code.
IOrganizationService service = localContext.OrganizationService;
IPluginExecutionContext context = localContext.PluginExecutionContext;
// Get Primary Entity
EntityReference target = (EntityReference)context.InputParameters["Target"];
if (target.LogicalName == "new_project" || target.LogicalName == "systemuser")
{
Relationship relationShip = (Relationship)context.InputParameters["Relationship"];
// Get Related Entities
EntityReferenceCollection relatedentities = (EntityReferenceCollection)context.InputParameters["RelatedEntities"];
foreach (EntityReference rel in relatedentities)
{
// Check Related Entity Logical & Schema name
if (rel.LogicalName == "new_project" && relationShip.SchemaName == "new_systemuser_new_project")
{
Entity user = service.Retrieve("systemuser", target.Id, new ColumnSet(true));
Entity project = service.Retrieve("new_project", rel.Id, new ColumnSet(true));
// Grant access
RevokeAccessRequest revokeAccessRequest = new RevokeAccessRequest
{
Revokee=target,
Target = rel
};
service.Execute(revokeAccessRequest);
}
}
}
But I am not able to debug this plugin using plugin registration tool.It returns the message "App Domain Unloaded".
I don't know what is wrong with this plugin. Please Help.
I have solved this problem.
I had registered this plugin in update message, then changed RegisterFile.crmregister
MessageName="Disassociate" and PrimaryEntityName=""
also changed the default constructor of the my plugin class.
base.RegisteredEvents.Add(new Tuple<int, string, string, Action<LocalPluginContext>>(10, "Disassociate", "", new Action<LocalPluginContext>(ExecutePreValidateProjectUpdate)));
Hope this will help someone facing same problem.

Using a RequestFilter to Perform Custom Authentication in ServiceStack

Brand new to ServiceStack, so forgive me if this is an easy one.
I am writing an API that will use a custom HTTP header for authentication information. I've added a RequestFilter like so:
RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
if(httpReq.Headers["MyMagicHeader"] != "magic")
{
throw HttpError.Unauthorized("Unauthorized");
}
else
{
//TODO: Populate a "Client" object accessible by the Service
}
});
My question is, how can I now provide the service in question with the "Client" object that I create based on the value in the magic header?
From the looks of it, my only option is passing this information in via the DTO. So I thought about adding a base class that all my DTOs inherit from, and this base class would contain a Client property.
Is that the correct approach?
The way to pass any information that's available in all Filters and Service in the same request is to use the httpReq.Items object Dictionary, e.g. Dictionary<string,object>"
RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
if(httpReq.Headers["MyMagicHeader"] != "magic")
{
throw HttpError.Unauthorized("Unauthorized");
}
else
{
//TODO: Populate a "Client" object accessible by the Service
httpReq.Items["MagicToken"] = CreateMagicValue(httpReq.Headers["MyMagicHeader"]);
}
});

Identify If User is Group using the client object model

I have a sharepoint field in a list that can be either a user or a group. Using the Server Object Model, I can identify easily whether the user is a group or not.
However, I cannot find a way to achieve this using the Managed Client Object model. Is there a way to know.
I only managed to make it work by looping the list of groups and checking if the there is a group with the name. Howver, this is not exactly correct or efficient. Maybe there is a way to find out using the ListItem of the user. But I did not see any fields that show that user is administrator. I have also tried EnsureUser. This crashes if the user is not a group. So I could work out by using a try/catch but this would be very bad programming.
Thanks,
Joseph
To do this get the list of users from ClientContext.Current.Web.SiteUserInfoList and then check the ContentType of each item that is returned to determine what it is.
Checking the content type is not very direct though, because all you actually get back from each item is a ContentTypeID, which you then have to look-up against the content types of the user list at ClientContext.Current.Web.SiteUserInfoList.ContentTypes. That look-up will return a ContentType object, and you can read from the Name property of that object to see what the list item is.
So an over simplified chunk of code to do this would be:
using Microsoft.SharePoint.Client;
...
ClientContext context = ClientContext.Current;
var q = from i in context.Web.SiteUserInfoList.GetItems(new CamlQuery()) select i;
IEnumerable<ListItem> Items = context.LoadQuery(q);
context.ExecuteQueryAsync((s, e) => {
foreach (ListItem i in Items) {
//This is the important bit:
ContentType contenttype = context.Web.SiteUserInfoList.ContentTypes.GetById(i["ContentTypeId"].ToString());
context.Load(contenttype); //It's another query so we have to load it too
switch (contenttype.Name) {
case "SharePointGroup":
//It's a SharePoint group
break;
case "Person":
//It's a user
break;
case "DomainGroup":
//It's an Active Directory Group or Membership Role
break;
default:
//It's a mystery;
break;
}
}
},
(s, e) => { /* Query failed */ }
);
You didn't specify your platform, but I did all of this in Silverlight using the SharePoint client object model. It stands to reason that the same would be possible in JavaScript as well.
Try Microsoft.SharePoint.Client.Utilities.Utility.SearchPrincipals(...):
var resultPrincipals = Utility.SearchPrincipals(clientContext, clientContext.Web, searchString, PrincipalType.All, PrincipalSource.All, null, maxResults);
The return type, PrincipalInfo, conveniently has a PrincipalType property which you can check for Group.

Orchard CMS: Connect List content part to content item

I have been struggling with what I thought would be simple.
I have a content type called Supplier. This supplier has contact information containing two addresses, one for Correspondence Address and one for Visiting Address. The supplier has also several locations, like location north and location south. A location is also an address. So basically I have a content item Supplier with a lot of addresses and all of them with their own type.
Migration:
public int Create() {
//Creating the Location contentrecord, contentpart and contenttype
SchemaBuilder.CreateTable("LocationPartRecord", table => table
.ContentPartRecord()
.Column<int>("LocationsPartRecord_id")
);
ContentDefinitionManager.AlterPartDefinition("LocationPart", part => part
.Attachable(false)
.WithField("LocationName", f => f.OfType("TextField"))
.WithField("AddressLine1", f => f.OfType("TextField"))
.WithField("AddressLine2", f => f.OfType("TextField"))
.WithField("Zipcode", f => f.OfType("TextField"))
.WithField("City", f => f.OfType("TextField"))
.WithField("Country", f => f.OfType("TextField")));
ContentDefinitionManager.AlterTypeDefinition("Location",
cfg => cfg
.WithPart("CommonPart")
.WithPart("LocationPart")
);
//Creating the Locations 'container' contentpart
SchemaBuilder.CreateTable("LocationsPartRecord", table => table
.ContentPartRecord()
);
ContentDefinitionManager.AlterPartDefinition("LocationsPart", builder => builder.Attachable());
//Creating the supplier. Specific supplier contentfields can be added later. Doing records, so I can add
//datafields later that are not contentfields
SchemaBuilder.CreateTable("SupplierPartRecord", table => table
.ContentPartRecord());
ContentDefinitionManager.AlterPartDefinition("SupplierPart", part => part
.Attachable(false)
);
ContentDefinitionManager.AlterTypeDefinition("Supplier", builder => builder
.Creatable()
.Draftable()
.WithPart("CommonPart")
.WithPart("TitlePart")
.WithPart("BodyPart")
.WithPart("AutoroutePart", partBuilder =>
partBuilder.WithSetting("AutorouteSettings.AllowCustomPattern", "true")
.WithSetting("AutorouteSettings.PatternDefinitions", "[{Name:'Supplier', Pattern: 'aanbieders/{Content.Slug}', Description: 'aanbieders/supplier-name'}]")
.WithSetting("AutorouteSettings.DefaultPatternIndex", "0"))
.WithPart("SupplierPart")
.WithPart("LocationsPart"));
return 1;
}
Models:
*LocationPartRecord and LocationPart *
public class LocationPartRecord:ContentPartRecord {
public virtual LocationsPartRecord LocationsPartRecord { get; set; }
}
public class LocationPart:ContentPart<LocationPartRecord> {
LocationsPartRecord LocationsPartRecord {
get { return Record.LocationsPartRecord; }
set { Record.LocationsPartRecord = value; }
}
}
LocationsPartRecord and LocationsPart (container)
public class LocationsPartRecord:ContentPartRecord {
public LocationsPartRecord()
{
Locations = new List<LocationPartRecord>();
}
[CascadeAllDeleteOrphan]
public virtual IList<LocationPartRecord> Locations { get; set; }
}
public class LocationsPart:ContentPart<LocationsPartRecord> {
public LocationsPart() {
Locations = new List<LocationPart>();
}
public readonly LazyField<IList<LocationPart>> _locations = new LazyField<IList<LocationPart>>();
public IList<LocationPart> Locations {
get { return _locations.Value; }
set { _locations.Value = value; }
}
}
From here I am stuck. I would like to see when Creating a new supplier, I get a screen containing all the content item fields for supplier and a list of locations, with the ability to create, delete or update a location.
I don't need the code to be spelled out, but a direction would suffice. Which drivers, controllers and views should I create. This is only for admin console. For frontend the locations need to be displayed and not edited.
I don't think there will be any way to get the functionality you're after without custom coding. As you have suggested, the comments module could be a good example to copy. The Controllers in the comments module are only to manage all of the comments in their own admin pages, separate to the content items they belong to. The edit / display of the comments is still provided through the drivers and handlers.
Using the Comments module analogy:
CommentsPart = AddressesPart - This would be added to your Supplier content type
CommentPart = AddressPart - This would be added to your Address content type
You could strip out a lot of the extra functionality that is included for managing comments and just copy the drivers, handlers, views and models for these two parts.
I have seen some gallery modules that may allow you to build these relationships through the admin interface, however I haven't used it myself:
http://gallery.orchardproject.net/List/Modules/Orchard.Module.Downplay.Mechanics
Address shouldn't be a part, it should be a field. This way, you can have more than one, and each can be named.
Don't know if this would be helpful (and the site appears to be down - but Google has a cached version if you are patient for it to load), but there is a good blog about exactly your situation. It's Skywalkers excellent Web Shop series. I believe Part 8 contains the code related to multiple addresses (uses Address and Addresses). This seems to involve your problem, and the code may be what you need.
In case you have trouble getting to the site, there is also a CodePlex repository for the code. Additionally, Bertrand's Nwazet Commerce module might have similar code.

Resources