Accesing and saving dynamic object - c#

I want send from service to service object:
public abstract class Notification : AggregateRoot
{
public string Title { get; set; }
public string Content { get; set; }
public DateTime Created { get; set; }
public NotificationType NotificationType { get; set; }
}
public class Alert : Notification
{
public object LinkedObject { get; set; }
public bool WasSeen { get; set; }
}
And from my unit test:
[Theory, AutoNSubstituteData]
public async void Send_NotificationIsAlertTypeDocumentDontExist_DocumentShouldBeCreatedAndNotificationSaved(
IDocumentDbRepository<AlertsDocument> repository,
CampaignAlertsSender sender,
Alert notification
)
{
// Arrange
notification.NotificationType = NotificationType.Alert;
notification.LinkedObject = new
{
MerchantId = Guid.NewGuid()
};
repository.GetItemAsync(Arg.Any<Expression<Func<AlertsDocument, bool>>>()).Returns((Task<AlertsDocument>) null);
// Act
await sender.SendAsync(notification);
// Assert
await repository.Received(1).GetItemAsync(Arg.Any<Expression<Func<AlertsDocument, bool>>>());
await repository.Received(1).CreateItemAsync(Arg.Any<AlertsDocument>());
}
Look at the linkedobject it is object but I make it with new. And send it to service.
public override async Task SendAsync(Notification notification)
{
if(notification == null)
throw new ArgumentNullException(nameof(notification));
var alert = notification as Alert;
if(alert == null)
throw new ArgumentException();
var linkedObject = alert.LinkedObject as dynamic;
Guid merchantId = Guid.Parse(linkedObject.MerchantId); // here is problem! linkedObject "object" dont have "MerchantId".
var document = await Repository.GetItemAsync(doc => doc.MerchantId == merchantId);
if (document == null)
{
document = new AlertsDocument
{
MerchantId = merchantId,
Entity = new List<Alert>()
};
document.Entity.Add(alert);
}
}
Here is problem! linkedObject "object" dont have "MerchantId".
But why? While debuging I see the value MerchantId in linkedObject.
How to do it?
Error:
An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in mscorlib.dll but was not handled in user code
Additional information: 'object' does not contain a definition for 'MerchantId'

LinkedObject is created as an anonymous type which are generated as internal types. If the code accessing the object is not in the same assembly then you will get that error. The debugger can see because it is using reflection but when you try to access it via dynamic you get the error (again because anonymous types are generated as internal).
You however can still get to it via reflection as well.
var linkedObject = alert.LinkedObject as dynamic;
Guid merchantId = (Guid)linkedObject.GetType()
.GetProperty("MerchantId")
.GetValue(linkedObject, null);
but this can get messy very fast.
If you take a look at my answer provided here
How do you unit test ASP.NET Core MVC Controllers that return anonymous objects?
A dynamic wrapper which uses reflection under the hood to access the properties of the anonymous type was used.
The same theory applies and you could probably use that wrapper to access the properties of the linkedObject.
var linkedObject = new DynamicObjectResultValue(alert.LinkedObject);
Guid merchantId = (Guid)linkedObject.MerchantId;

From your code it seems that MerchantId is already a Guid, so you just need to cast it, instead of parsing:
var linkedObject = (dynamic)alert.LinkedObject;
var merchantId = (Guid)linkedObject.MerchantId;

Related

Automapper ignore all mapping where value is null or default value

I'm using automapper via DI and want to have generic rules for all the mappings found in my solution. Belows example is of a single class, however I've got hundreds of classes to maintain and therefore want to add it to the mapper, not the mapping profile.
So far, I can handle Null values as follows :
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
Which in my example below, allows for the null update to persist past all the subsiquent updates. What I want is a generic rule ( or statement) for the default value of Guids, DateTimeOffset and Int. In my example below, update 1 and 2 are lost once update 3 is mapped.
using System;
using Autofac;
using AutoMapper;
namespace AutoMapperProblem
{
public class Program
{
static void Main(string[] args)
{
var builder = new ContainerBuilder();
builder.RegisterInstance(new MapperConfiguration(cfg =>
{
cfg.AddMaps(typeof(Program).Assembly);
cfg.ForAllMaps((obj, cnfg) =>
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember) => srcMember != null)));
}).CreateMapper()).As<IMapper>().SingleInstance();
var container = builder.Build();
var mapper = container.Resolve<IMapper>();
var MainObject = new MainObject();
//this update persists pass all updates
var updateNull = new UpdateObject { NullGuid = Guid.NewGuid() };
mapper.Map(updateNull, MainObject);
var update1 = new UpdateObject { DateTimeOffset = DateTimeOffset.Now };
mapper.Map(update1, MainObject);
var update2 = new UpdateObject { Guid = Guid.NewGuid() };
mapper.Map(update2, MainObject);
var update3 = new UpdateObject { Int = 10 };
mapper.Map(update3, MainObject);
}
}
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<MainObject, UpdateObject>()
.ReverseMap();
}
}
public class MainObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
public class UpdateObject
{
public Guid? NullGuid { get; set; }
public Guid Guid { get; set; }
public int Int { get; set; }
public DateTimeOffset DateTimeOffset { get; set; }
}
}
I want it to work based on the type, eg ints, DateTimeOffsets and Guid's .
Heres a working example on DotNetFiddle : https://dotnetfiddle.net/Zh0ta6
Thanks for any help with this
The problem is that you cannot check value types against null in your opts.Condition() check, as they always have a value. Also, you can't use the default keyword directly in this specific context because the variable type is object. That will turn default to null again, and fail.
Depending on what you want to do, you can check if the destination already have a non-default value and do NOT replace it with any source value, which might be the default value of the type (0 for int, 0000...-000 for Guid, etc.). The check could look like this:
cnfg.ForAllMembers(opts => opts.Condition((src, dest, srcMember, destMember) =>
{
object destDefaultValue = null;
if (destMember != null)
{
Type destType = destMember.GetType();
if (destType.IsValueType)
{
destDefaultValue = Activator.CreateInstance(destType);
}
else
{
destDefaultValue = null;
}
}
bool destinationHasNoValue = Object.Equals(destMember, destDefaultValue);
return destinationHasNoValue;
})
The first part is calculating the default value for the given destination value, based on the type of the destination value. The latter part is checking if the destination already have a non-default value or not. Consecutive calls of Map() with the same target object will fill the "missing" property values until all the property values are set (in this example). If necessary, check the srcMember value and it's default type as well to fine-tune when the value should be copied or not.

In gRPC unable to bind enum values from message to dontnet core Dto

I need to define a string array type dataType in Grpc message. not sure how to do. right now i am doing it as a
repeated string Title= 1,
here i need name field as string array Type. But it is showing error that it is, field is readonly type when bind data in it:
public override async Task<UserResponse> CreateUser(
UserModel request, ServerCallContext context)
{
var eventResponse = new UserResponse();
var createCmd = new CreateUserCommand
{
Model = new UserDto
{
Title = request.Title,
Id = request.Id,
}
}
}
here in title i need to bind data
The generated code from protoc here gives you something like:
private readonly RepeatedField<string> title_ = new RepeatedField<string>();
[DebuggerNonUserCodeAttribute]
public RepeatedField<string> Title {
get { return title_; }
}
So: Title is indeed read-only. This means that instead of assigning it, you should explore what APIs exist for adding to it - i.e.
var user = new UserDto
{
Id = request.Id,
}
user.Title.Add(request.Title);
// or AddRange, etc
You may still be able to use initializer syntax, too:
new UserDto
{
Id = request.Id,
Title = { request.Title }
}
(which is an .Add)

Custom generic DTO Assembler

I'm trying to create a method which can be used like this:
Assembler.CreateTransfer(i);
i is an item inside a dynamic collection.
i is a domain object.
CreateTransfer() should return an instance of i type + Transfer (if my domain object is User, then it should return an instance of UserTransfer.
As for my models is like this:
public abstract class UserBase {
public long UserId { get; set; }
public string Username { get; set; }
}
public class UserTransfer : UserBase, ITransferObject { }
public partial class User : UserTransfer, IModelBase, IDomainObject {
public User() {
Roles = new List<Role>();
}
public virtual ICollection<Role> Roles { get; set; }
}
So far, I've accomplished this:
public static TTransfer CreateTransfer<TTransfer, TDomain>(TDomain d)
where TTransfer : ITransferObject
where TDomain : IDomainObject
{ ... }
This works, because I know the type of TTransfer and TDomain.
And I can call it like: CreateTransfer<UserTransfer, User>(d).
The problem comes when I try create the dto without specifying any type:
CreateTransfer(d);
Of course I've added an overload for this task and I hope I can magically accomplish the following:
public static ITransferObject CreateTransfer(IDomainObject d) {
// do magic
return CreateTransfer<transfer, domain>(d);
}
But in the real world, this is as far as I could get:
public static ITransferObject CreateTransfer(IDomainObject d) {
var dType = d.GetType();
var assemblyName = dType.Assembly.FullName;
var domainName = dType.FullName;
var transferName = domainName + "Transfer";
var domain = Activator.CreateInstance(assemblyName, domainName).UnWrap();
var transfer = Activator.CreateInstance(assemblyName, transferName).UnWrap();
return CreateTransfer< ?, ?>(d); // Problem
}
Both domain and transfer objects are being created correctly.
The main question is: Is there any way to be able to call CreateTransfer<domain, transfer>(d)? Any help will be appreciated.
You can use reflection to call the generic method.
Something like this:
var method = typeof(ClassThatDefinesCreateTransfer)
.GetMethods()
.Single(x => x.Name == "CreateTransfer" &&
x.IsGenericMethodDefinition)
.MakeGenericMethod(dType, transferType);
return (ITransferObject )method.Invoke(null, new object[] { d });
You probably want to cache at least the result of typeof(ClassThatDefinesCreateTransfer).GetMethods().

Glass.Mapper and creating Sitecore items using Interfaces

This was a question from Twitter:
What is the pattern for creating items from interface models? Using sitecoreService.Create<T,K>(T newItem, K parent) where T is an interface, requires adding a class to create new items. Is there a way to create them directly from the interface?
This is a challenge because you need a concrete version of your interface to write values to before saving them. The simple solution is to use a mocking framework like NSubstitute, uisng the following interface:
[SitecoreType(TemplateId = "{7FC4F278-ADDA-4683-944C-554D0913CB33}", AutoMap = true)]
public interface StubInterfaceAutoMapped
{
Guid Id { get; set; }
Language Language { get; set; }
string Path { get; set; }
int Version { get; set; }
string Name { get; set; }
string StringField { get; set; }
}
I can create the following test:
[Test]
public void Create_UsingInterface_CreatesANewItem()
{
//Assign
string parentPath = "/sitecore/content/Tests/SitecoreService/Create";
string childPath = "/sitecore/content/Tests/SitecoreService/Create/newChild";
string fieldValue = Guid.NewGuid().ToString();
var db = Sitecore.Configuration.Factory.GetDatabase("master");
var context = Context.Create(Utilities.CreateStandardResolver());
context.Load(new SitecoreAttributeConfigurationLoader("Glass.Mapper.Sc.Integration"));
var service = new SitecoreService(db);
using (new SecurityDisabler())
{
var parentItem = db.GetItem(parentPath);
parentItem.DeleteChildren();
}
var parent = service.GetItem<StubClass>(parentPath);
var child = Substitute.For<StubInterfaceAutoMapped>();
child.Name = "newChild";
child.StringField = fieldValue;
//Act
using (new SecurityDisabler())
{
service.Create(parent, child);
}
//Assert
var newItem = db.GetItem(childPath);
Assert.AreEqual(fieldValue, newItem["StringField"]);
using (new SecurityDisabler())
{
newItem.Delete();
}
Assert.AreEqual(child.Name, newItem.Name);
Assert.AreEqual(child.Id, newItem.ID.Guid);
}
This works because of the way that Glass.Mapper resolves the type to be mapped:
/// <summary>
/// Gets the type configuration.
/// </summary>
/// <param name="obj">The obj.</param>
/// <returns>AbstractTypeConfiguration.</returns>
public AbstractTypeConfiguration GetTypeConfiguration(object obj)
{
var type = obj.GetType();
var config = TypeConfigurations.ContainsKey(type) ? TypeConfigurations[type] : null;
if (config != null) return config;
//check base type encase of proxy
config = TypeConfigurations.ContainsKey(type.BaseType) ? TypeConfigurations[type.BaseType] : null;
if (config != null) return config;
//check interfaces encase this is an interface proxy
string name = type.Name;
//ME - I added the OrderByDescending in response to issue 53
// raised on the Glass.Sitecore.Mapper project. Longest name should be compared first
// to get the most specific interface
var interfaceType = type.GetInterfaces().OrderByDescending(x=>x.Name.Length).FirstOrDefault(x => name.Contains(x.Name));
if (interfaceType != null)
config = TypeConfigurations.ContainsKey(interfaceType) ? TypeConfigurations[interfaceType] : null;
return config;
}
Notice that if it can't find a direct type match it will start to determine the type based on the interfaces associated to the passed in type and use the first it finds base on the name. Now I suspect that NSubstitute works because it also uses Castle Dynamic Proxies, it would be interesting to test it with other mocking frameworks.

Selectively Whitelisting Model Fields to Bind

(I realize this question is very similar to How to whitelist/blacklist child object fields in the ModelBinder/UpdateModel method? but my situation is slightly different and there may be a better solution available now that wasn't then.)
Our company sells web-based software that is extremely configurable by the end-user. The nature of this flexibility means that we must do a number of things at run time that would normally be done at compile time.
There are some rather complex rules regarding who has read or read/write access to most everything.
For instance, take this model that we would like to create:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace j6.Business.Site.Models
{
public class ModelBindModel
{
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string FirstName { get; set; }
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string MiddleName { get; set; }
[Required]
[Whitelist(ReadAccess = true, WriteAccess = true)]
public string LastName { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSalary, WriteAccess = User.CanWriteSalary)]
public string Salary { get; set; }
[Required]
[Whitelist(ReadAccess = User.CanReadSsn, WriteAccess = User.CanWriteSsn)]
public string Ssn { get; set; }
[Required]
public string SirNotAppearingOnThisPage { get; set; }
}
}
In the controller, it is not difficult to "unbind" things manually.
var resetValue = null;
modelState.Remove(field);
pi = model.GetType().GetProperty(field);
if (pi == null)
{
throw new Exception("An exception occured in ModelHelper.RemoveUnwanted. Field " +
field +
" does not exist in the model " + model.GetType().FullName);
}
// Set the default value.
pi.SetValue(model, resetValue, null);
Using HTML helpers, I can easily access the model metadata and suppress rendering of any fields the user does not have access to.
The kicker: I can't figure out how to access the model metadata anywhere in the CONTROLLER itself to prevent over-posting.
Note that using [Bind(Include...)] is not a functional solution, at least not without additional support. The properties to Include are run-time (not compile time) dependent, and excluding the property does not remove it from the validation.
ViewData.Model is null
ViewData.ModelMetaData is null
[AllowAnonymous]
[HttpPost]
// [Bind(Exclude = "Dummy1" + ",Dummy2")]
public ViewResult Index(ModelBindModel dto)
{
zzz.ModelHelper.RemoveUnwanted(ModelState, dto, new string[] {"Salary", "Ssn"});
ViewBag.Method = "Post";
if (!ModelState.IsValid)
{
return View(dto);
}
return View(dto);
}
Any suggestions on how to access the Model MetaData from the controller? Or a better way to whitelist properties at run time?
Update:
I borrowed a page from this rather excellent resource:
http://www.dotnetcurry.com/ShowArticle.aspx?ID=687
With a model that looks like this:
[Required]
[WhiteList(ReadAccessRule = "Nope", WriteAccessRule = "Nope")]
public string FirstName { get; set; }
[Required]
[WhiteList(ReadAccessRule = "Database.CanRead.Key", WriteAccessRule = "Database.CanWrite.Key")]
public string LastName { get; set; }
The class:
public class WhiteList : Attribute
{
public string ReadAccessRule { get; set; }
public string WriteAccessRule { get; set; }
public Dictionary<string, object> OptionalAttributes()
{
var options = new Dictionary<string, object>();
var canRead = false;
if (ReadAccessRule != "")
{
options.Add("readaccessrule", ReadAccessRule);
}
if (WriteAccessRule != "")
{
options.Add("writeaccessrule", WriteAccessRule);
}
if (ReadAccessRule == "Database.CanRead.Key")
{
canRead = true;
}
options.Add("canread", canRead);
options.Add("always", "be there");
return options;
}
}
And adding these lines to the MetadataProvider class mentioned in the link:
var whiteListValues = attributes.OfType<WhiteList>().FirstOrDefault();
if (whiteListValues != null)
{
metadata.AdditionalValues.Add("WhiteList", whiteListValues.OptionalAttributes());
}
Finally, the heart of the system:
public static void DemandFieldAuthorization<T>(ModelStateDictionary modelState, T model)
{
var metaData = ModelMetadataProviders
.Current
.GetMetadataForType(null, model.GetType());
var props = model.GetType().GetProperties();
foreach (var p in metaData.Properties)
{
if (p.AdditionalValues.ContainsKey("WhiteList"))
{
var whiteListDictionary = (Dictionary<string, object>) p.AdditionalValues["WhiteList"];
var key = "canread";
if (whiteListDictionary.ContainsKey(key))
{
var value = (bool) whiteListDictionary[key];
if (!value)
{
RemoveUnwanted(modelState, model, p.PropertyName);
}
}
}
}
}
To recap my interpretation of your question:
Field access is dynamic; some users may be able to write to a field and some may not.
You have a solution to control this in the view.
You want to prevent a malicious form submission from sending restricted properties, which the model binder will then assign to your model.
Perhaps something like this?
// control general access to the method with attributes
[HttpPost, SomeOtherAttributes]
public ViewResult Edit( Foo model ){
// presumably, you must know the user to apply permissions?
DemandFieldAuthorization( model, user );
// if the prior call didn't throw, continue as usual
if (!ModelState.IsValid){
return View(dto);
}
return View(dto);
}
private void DemandFieldAuthorization<T>( T model, User user ){
// read the model's property metadata
// check the user's permissions
// check the actual POST message
// throw if unauthorized
}
I wrote an extension method a year or so ago that has stood me in good stead a couple of times since. I hope this is of some help, despite not being perhaps the full solution for you. It essentially only allows validation on the fields that have been present on the form sent to the controller:
internal static void ValidateOnlyIncomingFields(this ModelStateDictionary modelStateDictionary, FormCollection formCollection)
{
IEnumerable<string> keysWithNoIncomingValue = null;
IValueProvider valueProvider = null;
try
{
// Transform into a value provider for linq/iteration.
valueProvider = formCollection.ToValueProvider();
// Get all validation keys from the model that haven't just been on screen...
keysWithNoIncomingValue = modelStateDictionary.Keys.Where(keyString => !valueProvider.ContainsPrefix(keyString));
// ...and clear them.
foreach (string errorKey in keysWithNoIncomingValue)
modelStateDictionary[errorKey].Errors.Clear();
}
catch (Exception exception)
{
Functions.LogError(exception);
}
}
Usage:
ModelState.ValidateOnlyIncomingFields(formCollection);
And you'll need a FormCollection parameter on your ActionResult declaration, of course:
public ActionResult MyAction (FormCollection formCollection) {

Categories