Custom model binder with inheritance using Web API and RavenDB - asp.net-mvc-4

I'm developing a simple web app where I need to bind all types implementing and interface of a specific type. My interface has one single property like this
public interface IContent {
string Id { get;set; }
}
a common class using this interface would look like this
public class Article : IContent {
public string Id { get;set; }
public string Heading { get;set; }
}
to be clean here the article class is just one of many different classes implementing IContent so therefor I need a generic way of storing and updating these types.
So in my controller I have the put method like this
public void Put(string id, [System.Web.Http.ModelBinding.ModelBinder(typeof(ContentModelBinder))] IContent value)
{
// Store the updated object in ravendb
}
and the ContentBinder
public class ContentModelBinder : System.Web.Http.ModelBinding.IModelBinder {
public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext) {
actionContext.ControllerContext.Request.Content.ReadAsAsync<Article>().ContinueWith(task =>
{
Article model = task.Result;
bindingContext.Model = model;
});
return true;
}
}
The code above does not work because it does not seem to get hold of the Heading property even though if I use the default model binder it binds the Heading correctly.
So, in the BindModel method I guess I need to load the correct object from ravendb based on the Id and then update the complex object using some kind of default model binder or so? This is where I need some help.

Marcus, following is an example which would work fine for both Json and Xml formatter.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
// this is ignored
public DateTime DateOfBirth { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}

I used #kiran-challa solution and added TypeNameHandling on Json media type formatter's SerializerSettings.

Related

Deserialize enums using the EnumMember value in AspNet [FromQuery] model binding

I have an endpoint in .NET 6 Microsoft.NET.Sdk.Web project that deserialize query strings into a .NET object by using the standard [FromQuery]
[Route("[controller]")]
public class SamplesController
: ControllerBase
{
[HttpGet]
public IActionResult Get([FromQuery]QueryModel queryModel)
{
if (!queryModel.Status.HasValue)
{
return BadRequest("Problem in deserialization");
}
return Ok(queryModel.Status.Value.GetEnumDisplayName());
}
}
The model contains an enum
public class QueryModel
{
/// <summary>
/// The foo parameter
/// </summary>
/// <example>bar</example>
public string? Foo { get; init; } = null;
/// <summary>
/// The status
/// </summary>
/// <example>on-hold</example>
public Status? Status { get; set; } = null;
}
And the enum has EnumMember attributes which value I want to use to deserialize from.
public enum Status
{
[EnumMember(Value = "open")]
Open,
[EnumMember(Value = "on-hold")]
OnHold
}
By default, .NET 6 does not take into consideration the EnumMember when deserializing.
The goal is to be able to send requests such as
http://localhost:5000/Samples?Foo=bar&Status=on-hold
and have the controller's action deserialize the QueryModel with the proper Status.OnHold value by using its EnumMember
I have tried without luck an extensions library that contains a converter, but the converter is not getting triggered when using [FromQuery]. See https://github.com/Macross-Software/core/issues/30
I have added a project to reproduce problem and as a sandbox to provide a solution**
https://gitlab.com/sunnyatticsoftware/issues/string-to-enum-mvc/-/tree/feature/1-original-problem
NOTE: I would need a solution where the Enum and the does not require any external dependency (just .NET sdk).
A custom Enum converter might be your choice. By leveraging the existing EnumConverter class what we need is to have a customized ConvertFrom method:
public class CustomEnumConverter : EnumConverter
{
public CustomEnumConverter([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicFields)] Type type) : base(type)
{
}
public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string strValue)
{
try
{
foreach (var name in Enum.GetNames(EnumType))
{
var field = EnumType.GetField(name);
if (field != null)
{
var enumMember = (EnumMemberAttribute)(field.GetCustomAttributes(typeof(EnumMemberAttribute), true).Single());
if (strValue.Equals(enumMember.Value, StringComparison.OrdinalIgnoreCase))
{
return Enum.Parse(EnumType, name, true);
}
}
}
}
catch (Exception e)
{
throw new FormatException((string)value, e);
}
}
return base.ConvertFrom(context, culture, value);
}
}
And then decorate the converter to your Model class:
[TypeConverter(typeof(CustomEnumConverter))]
public enum Status
{
[EnumMember(Value = "open")]
Open,
[EnumMember(Value = "on-hold")]
OnHold
}
then we can get the "on-hold" parsed. You might also want to override the ConverTo() for printing the EnumMember value to swagger. It is a bit hacky, but if you want a pure .NET solution this should be one of the minimal viable solutions.
Following the documentation guide Custom Model Binding in ASP.NET Core, you can create your own versions of Microsoft's classes EnumTypeModelBinderProvider, EnumTypeModelBinder (and base class SimpleTypeModelBinder) that replace incoming enum value names that have been renamed via EnumMemberAttribute with the original enum names before binding:
// Begin code for enum model binding
public class EnumMemberEnumTypeModelBinderProvider : IModelBinderProvider
{
public EnumMemberEnumTypeModelBinderProvider(MvcOptions options) { }
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsEnum)
{
var enumType = context.Metadata.UnderlyingOrModelType;
Debug.Assert(enumType.IsEnum);
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
if (EnumExtensions.TryGetEnumMemberOverridesToOriginals(enumType, out var overridesToOriginals))
return new EnumMemberEnumTypeModelBinder(suppressBindingUndefinedValueToEnumType: true, enumType, loggerFactory, overridesToOriginals);
}
return null;
}
}
public class EnumMemberEnumTypeModelBinder : ExtensibleSimpleTypeModelBinder
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/EnumTypeModelBinder.cs#L58
readonly Type enumType;
readonly bool isFlagged;
readonly Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals;
readonly TypeConverter typeConverter;
public EnumMemberEnumTypeModelBinder(bool suppressBindingUndefinedValueToEnumType, Type modelType, ILoggerFactory loggerFactory, Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals) : base(modelType, loggerFactory)
{
this.enumType = Nullable.GetUnderlyingType(modelType) ?? modelType;
if (!this.enumType.IsEnum)
throw new ArgumentException();
this.isFlagged = Attribute.IsDefined(enumType, typeof(FlagsAttribute));
this.overridesToOriginals = overridesToOriginals ?? throw new ArgumentNullException(nameof(overridesToOriginals));
this.typeConverter = TypeDescriptor.GetConverter(this.enumType);
}
protected override string? GetValueFromBindingContext(ValueProviderResult valueProviderResult) =>
EnumExtensions.ReplaceRenamedEnumValuesToOriginals(base.GetValueFromBindingContext(valueProviderResult), isFlagged, overridesToOriginals);
protected override void CheckModel(ModelBindingContext bindingContext, ValueProviderResult valueProviderResult, object? model)
{
if (model == null)
{
base.CheckModel(bindingContext, valueProviderResult, model);
}
else if (IsDefinedInEnum(model, bindingContext))
{
bindingContext.Result = ModelBindingResult.Success(model);
}
else
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(
valueProviderResult.ToString()));
}
}
private bool IsDefinedInEnum(object model, ModelBindingContext bindingContext)
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/EnumTypeModelBinder.cs#L58
var modelType = bindingContext.ModelMetadata.UnderlyingOrModelType;
// Check if the converted value is indeed defined on the enum as EnumTypeConverter
// converts value to the backing type (ex: integer) and does not check if the value is defined on the enum.
if (bindingContext.ModelMetadata.IsFlagsEnum)
{
var underlying = Convert.ChangeType(
model,
Enum.GetUnderlyingType(modelType),
CultureInfo.InvariantCulture).ToString();
var converted = model.ToString();
return !string.Equals(underlying, converted, StringComparison.OrdinalIgnoreCase);
}
return Enum.IsDefined(modelType, model);
}
}
public class ExtensibleSimpleTypeModelBinder : IModelBinder
{
// Adapted from https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Mvc/Mvc.Core/src/ModelBinding/Binders/SimpleTypeModelBinder.cs
private readonly TypeConverter _typeConverter;
private readonly ILogger _logger;
public ExtensibleSimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory) : this(type, loggerFactory, null) { }
public ExtensibleSimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory, TypeConverter? typeConverter)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
if (loggerFactory == null)
throw new ArgumentNullException(nameof(loggerFactory));
_typeConverter = typeConverter ?? TypeDescriptor.GetConverter(type);
_logger = loggerFactory.CreateLogger<ExtensibleSimpleTypeModelBinder>();
}
protected virtual string? GetValueFromBindingContext(ValueProviderResult valueProviderResult) => valueProviderResult.FirstValue;
/// <inheritdoc />
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
//_logger.AttemptingToBindModel(bindingContext);
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
//_logger.FoundNoValueInRequest(bindingContext);
// no entry
//_logger.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
try
{
var value = GetValueFromBindingContext(valueProviderResult);
object? model;
if (bindingContext.ModelType == typeof(string))
{
// Already have a string. No further conversion required but handle ConvertEmptyStringToNull.
if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value))
model = null;
else
model = value;
}
else if (string.IsNullOrWhiteSpace(value))
{
// Other than the StringConverter, converters Trim() the value then throw if the result is empty.
model = null;
}
else
{
model = _typeConverter.ConvertFrom(context: null,culture: valueProviderResult.Culture, value: value);
}
CheckModel(bindingContext, valueProviderResult, model);
//_logger.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask;
}
catch (Exception exception)
{
var isFormatException = exception is FormatException;
if (!isFormatException && exception.InnerException != null)
{
// TypeConverter throws System.Exception wrapping the FormatException,
// so we capture the inner exception.
exception = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
}
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName,exception, bindingContext.ModelMetadata);
// Were able to find a converter for the type but conversion failed.
return Task.CompletedTask;
}
}
/// <inheritdoc/>
protected virtual void CheckModel(
ModelBindingContext bindingContext,
ValueProviderResult valueProviderResult,
object? model)
{
// When converting newModel a null value may indicate a failed conversion for an otherwise required
// model (can't set a ValueType to null). This detects if a null model value is acceptable given the
// current bindingContext. If not, an error is logged.
if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
{
bindingContext.ModelState.TryAddModelError(
bindingContext.ModelName,
bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
valueProviderResult.ToString()));
}
else
{
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
// End code for enum model binding
/********************************************************/
// Begin general enum parsing code
public class CharMemoryComparer : IEqualityComparer<ReadOnlyMemory<char>>
{
public static CharMemoryComparer OrdinalIgnoreCase { get; } = new CharMemoryComparer(StringComparison.OrdinalIgnoreCase);
public static CharMemoryComparer Ordinal { get; } = new CharMemoryComparer(StringComparison.Ordinal);
readonly StringComparison comparison;
CharMemoryComparer(StringComparison comparison) => this.comparison = comparison;
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y) => MemoryExtensions.Equals(x.Span, y.Span, comparison);
public int GetHashCode(ReadOnlyMemory<char> obj) => String.GetHashCode(obj.Span, comparison);
}
public static partial class EnumExtensions
{
public const char FlagSeparatorChar = ',';
public const string FlagSeparatorString = ", ";
public static bool TryGetEnumMemberOverridesToOriginals(Type enumType, [System.Diagnostics.CodeAnalysis.NotNullWhen(returnValue: true)] out Dictionary<ReadOnlyMemory<char>, string>? overridesToOriginals)
{
if (enumType == null)
throw new ArgumentNullException(nameof(enumType));
if (!enumType.IsEnum)
throw new ArgumentException(nameof(enumType));
overridesToOriginals = null;
foreach (var name in Enum.GetNames(enumType))
{
if (TryGetEnumAttribute<EnumMemberAttribute>(enumType, name, out var attr) && !string.IsNullOrWhiteSpace(attr.Value))
{
overridesToOriginals = overridesToOriginals ?? new(CharMemoryComparer.OrdinalIgnoreCase);
overridesToOriginals.Add(attr.Value.AsMemory(), name);
}
}
return overridesToOriginals != null;
}
public static bool TryGetEnumAttribute<TAttribute>(Type type, string name, [System.Diagnostics.CodeAnalysis.NotNullWhen(returnValue: true)] out TAttribute? attribute) where TAttribute : System.Attribute
{
var member = type.GetMember(name).SingleOrDefault();
attribute = member?.GetCustomAttribute<TAttribute>(false);
return attribute != null;
}
public static string? ReplaceRenamedEnumValuesToOriginals(string? value, bool isFlagged, Dictionary<ReadOnlyMemory<char>, string> overridesToOriginals)
{
if (string.IsNullOrWhiteSpace(value))
return value;
var trimmed = value.AsMemory().Trim();
if (overridesToOriginals.TryGetValue(trimmed, out var #override))
value = #override;
else if (isFlagged && trimmed.Length > 0)
{
var sb = new StringBuilder();
bool replaced = false;
foreach (var n in trimmed.Split(EnumExtensions.FlagSeparatorChar, StringSplitOptions.TrimEntries))
{
ReadOnlySpan<char> toAppend;
if (overridesToOriginals.TryGetValue(n, out var #thisOverride))
{
toAppend = thisOverride.AsSpan();
replaced = true;
}
else
toAppend = n.Span;
sb.Append(sb.Length == 0 ? null : EnumExtensions.FlagSeparatorString).Append(toAppend);
}
if (replaced)
value = sb.ToString();
}
return value;
}
}
public static class StringExtensions
{
public static IEnumerable<ReadOnlyMemory<char>> Split(this ReadOnlyMemory<char> chars, char separator, StringSplitOptions options = StringSplitOptions.None)
{
int index;
while ((index = chars.Span.IndexOf(separator)) >= 0)
{
var slice = chars.Slice(0, index);
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
slice = slice.Trim();
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || slice.Length > 0)
yield return slice;
chars = chars.Slice(index + 1);
}
if ((options & StringSplitOptions.TrimEntries) == StringSplitOptions.TrimEntries)
chars = chars.Trim();
if ((options & StringSplitOptions.RemoveEmptyEntries) == 0 || chars.Length > 0)
yield return chars;
}
}
Then add the binder in ConfigureServices() like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new EnumMemberEnumTypeModelBinderProvider(options));
});
}
Notes:
EnumTypeModelBinder and base class SimpleTypeModelBinder provide no useful extension points to customize the parsing of the incoming value string, thus it was necessary to copy some of their logic.
Precisely emulating the logic of SimpleTypeModelBinder is somewhat difficult because it supports both numeric and textual enum values -- including mixtures of both for flags enums. The binder above retains that capability, but at a cost of also allowing original enum names to be bound successfully. Thus the values on-hold and onhold will be bound to Status.OnHold.
Conversely, if you do not want to support binding of numeric values for enums, you could adapt the code of JsonEnumMemberStringEnumConverter from this answer to System.Text.Json: How do I specify a custom name for an enum value?. Demo fiddle here. This approach also avoids binding to the original, unrenamed enum names.
Matching of override names with original enum names is case-insensitive, so override names that differ only in case are not supported.

How to keep user logged in after browser is closed

Every time I close the browser I need to log in again into this app. It is developed in .NET Core 2.0. I'm trying to let it logged in, like every other regular site.
I checked this post that may be useful, but since the code is quite different from this application I decided to create this post.
This is my security code:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
namespace Petito.Common
{
public interface IActivityContext
{
string ActivityID { get; }
IPrincipal User { get; }
}
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class ActivityIdentity : IIdentity
{
private string _name = null;
[JsonIgnore()]
private bool _isAuthenticated = false;
[JsonIgnore()]
private string _authenticationType = "";
public ActivityIdentity()
{
}
public ActivityIdentity(string name) : this(name, false, "")
{
}
internal ActivityIdentity(string name, bool isAuthenticated, string authenticationType)
{
this._name = name;
this._isAuthenticated = isAuthenticated;
this._authenticationType = authenticationType;
}
public string Name { get => _name; }
public bool IsAuthenticated { get => _isAuthenticated; }
public string AuthenticationType { get => _authenticationType; }
public static ActivityIdentity Unathenticated => new ActivityIdentity();
}
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class ActivityPrincipal : IPrincipal
{
private ActivityIdentity _activityIdentity = null;
private string[] _roles = null;
public ActivityPrincipal() : this(ActivityIdentity.Unathenticated, null)
{
}
public ActivityPrincipal(ActivityIdentity activityIdentity, params string[] roles)
{
_activityIdentity = activityIdentity;
_roles = roles;
}
public ActivityIdentity Identity => _activityIdentity;
IIdentity IPrincipal.Identity => _activityIdentity;
public bool IsInRole(string role)
{
if (_roles != null && _roles.Length > 0)
{
return _roles.Contains(role);
}
return false;
}
}
[JsonObject(MemberSerialization = MemberSerialization.Fields)]
public class ActivityContext : IDisposable, IActivityContext
{
private string _activityID = Guid.NewGuid().ToString();
private DateTime _startDate = DateTime.UtcNow;
private DateTime? _endDate = null;
private ActivityPrincipal _activityPrincipal = null;
public ActivityContext() : this(null)
{
}
public ActivityContext(IPrincipal principal)
{
_activityPrincipal = Convert(principal);
}
private ActivityPrincipal Convert(IPrincipal principal)
{
if (principal == null)
{
return new ActivityPrincipal();
}
var activityPrincipal = principal as ActivityPrincipal;
if (activityPrincipal != null)
{
return activityPrincipal;
}
var claimsPrincipal = principal as ClaimsPrincipal;
if (claimsPrincipal != null)
{
var roles = claimsPrincipal.Claims.Select(x => x.Value);
var p = new ActivityPrincipal(
new ActivityIdentity(claimsPrincipal.Identity.Name, claimsPrincipal.Identity.IsAuthenticated, claimsPrincipal.Identity.AuthenticationType)
, roles.ToArray()
);
return p;
}
throw new NotSupportedException($"Converting {principal.GetType()} not supported");
}
public void Dispose()
{
if (!_endDate.HasValue)
{
_endDate = DateTime.UtcNow;
}
}
public string ActivityID { get => _activityID; }
public DateTime StartDate { get => _startDate; }
public DateTime? EndDate { get => _endDate; }
public IPrincipal User
{
get
{
return _activityPrincipal;
}
}
}
}
Of course, I'll still try to figure out what's wrong with the code. Please if you need another part of the code other from that I posted let me know.
Thanks!

Setting up examples in Swagger

I am using Swashbuckle.AspNetCore.Swagger (1.0.0) and Swashbuckle.AspNetCore.SwaggerGen (1.0.0). I am trying to add default examples to my API following Default model example in Swashbuckle (Swagger). I created a new class file and added,
public class SwaggerDefaultValue : Attribute
{
public string ParameterName { get; set; }
public string Value { get; set; }
public SwaggerDefaultValue(string parameterName, string value)
{
this.ParameterName = parameterName;
this.Value = value;
}
}
public class AddDefaultValues : IOperationFilter
{
public void Apply(Operation operation, DataTypeRegistry dataTypeRegistry, ApiDescription apiDescription)
{
foreach (var param in operation.Parameters)
{
var actionParam = apiDescription.ActionDescriptor.GetParameters().First(p => p.ParameterName == param.Name);
if (actionParam != null)
{
var customAttribute = actionParam.ActionDescriptor.GetCustomAttributes<SwaggerDefaultValue>().FirstOrDefault();
if (customAttribute != null)
{
param.DefaultValue = customAttribute.Value;
}
}
}
}
}
but I get this error - AddDefaultValues does not implement interface member IOperationFilter.Apply(Operation, OperationFilterContext)
That link you are following is not for the Swashbuckle.AspNetCore version
Look in the correct project for the proper examples:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/search?q=IOperationFilter&unscoped_q=IOperationFilter

WebApi Model Binding For Inherited Types

I'm looking to handle model binding for an inherited type in WebApi, and what I'm really looking to do is to handle the binding using the default model binding (other than selecting the type where it's unable to do so), but I'm missing something fundamental.
So say I have the types:
public abstract class ModuleVM
{
public abstract ModuleType ModuleType { get; }
}
public class ConcreteVM : ModuleVM
{
}
Using an MVC controller, I would do something like this:
public class ModuleMvcBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
if (modelType == typeof(ModuleVM))
{
// Just hardcoding the type for simplicity
Type instantiationType = typeof(ConcreteVM);
var obj = Activator.CreateInstance(instantiationType);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, instantiationType);
bindingContext.ModelMetadata.Model = obj;
return obj;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
[AttributeUsage( AttributeTargets.Class | AttributeTargets.Enum | AttributeTargets.Interface | AttributeTargets.Parameter | AttributeTargets.Struct | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class ModuleMvcBinderAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new ModuleMvcBinder();
}
}
Then use the attribute on the controller and all is well, and I'm leveraging the DefaultModelBinder for the real work and I'm essentially just providing the correct object instantiation.
So how do I do the same for the WebApi version?
If I use a custom model binder (e.g. Error implementing a Custom Model Binder in Asp.Net Web API), my problem is (I believe) that in the BindModel method I haven't found a good way to use the "standard" http binding once I instantiate the object. I can do it specifically for JSON (Deserialising Json to derived types in Asp.Net Web API) or XML (Getting my Custom Model bound to my POST controller) as suggested in other posts, but it seems to me that's defeating the point since web api should be seperating that, and is - it just doesn't know how to determine the type. (All concrete types naturally are handled just fine.)
Am I overlooking something obvious I should be directing the BindModel call to after instantiating the object?
Following is an example where I have inheritance in my types and after some settings (like decorating with KnownType attributes, required by Xml formatter's datacontractserializer) and TypeNameHandling setting on Json formatter, we can expect consistent behavior across both xml/json requests.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace Service
{
class Service
{
private static HttpSelfHostServer server = null;
private static string baseAddress = string.Format("http://{0}:9095/", Environment.MachineName);
static void Main(string[] args)
{
HttpSelfHostConfiguration config = new HttpSelfHostConfiguration(baseAddress);
config.Routes.MapHttpRoute("Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
try
{
server = new HttpSelfHostServer(config);
server.OpenAsync().Wait();
Console.WriteLine("Service listenting at: {0} ...", baseAddress);
TestWithHttpClient("application/xml");
TestWithHttpClient("application/json");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Exception Details:\n{0}", ex.ToString());
}
finally
{
if (server != null)
{
server.CloseAsync().Wait();
}
}
}
private static void TestWithHttpClient(string mediaType)
{
HttpClient client = new HttpClient();
MediaTypeFormatter formatter = null;
// NOTE: following any settings on the following formatters should match
// to the settings that the service's formatters have.
if (mediaType == "application/xml")
{
formatter = new XmlMediaTypeFormatter();
}
else if (mediaType == "application/json")
{
JsonMediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.SerializerSettings.TypeNameHandling = TypeNameHandling.Objects;
formatter = jsonFormatter;
}
HttpRequestMessage request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Get;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
HttpResponseMessage response = client.SendAsync(request).Result;
Student std = response.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("GET data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std))
{
Console.WriteLine("both are equal");
}
client = new HttpClient();
request = new HttpRequestMessage();
request.RequestUri = new Uri(baseAddress + "api/students");
request.Method = HttpMethod.Post;
request.Content = new ObjectContent<Person>(StudentsController.CONSTANT_STUDENT, formatter);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
Student std1 = client.SendAsync(request).Result.Content.ReadAsAsync<Student>().Result;
Console.WriteLine("POST and receive data in '{0}' format", mediaType);
if (StudentsController.CONSTANT_STUDENT.Equals(std1))
{
Console.WriteLine("both are equal");
}
}
}
public class StudentsController : ApiController
{
public static readonly Student CONSTANT_STUDENT = new Student() { Id = 1, Name = "John", EnrolledCourses = new List<string>() { "maths", "physics" } };
public Person Get()
{
return CONSTANT_STUDENT;
}
// NOTE: specifying FromBody here is not required. By default complextypes are bound
// by formatters which read the body
public Person Post([FromBody] Person person)
{
if (!ModelState.IsValid)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState));
}
return person;
}
}
[DataContract]
[KnownType(typeof(Student))]
public abstract class Person : IEquatable<Person>
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
public bool Equals(Person other)
{
if (other == null)
return false;
if (ReferenceEquals(this, other))
return true;
if (this.Id != other.Id)
return false;
if (this.Name != other.Name)
return false;
return true;
}
}
[DataContract]
public class Student : Person, IEquatable<Student>
{
[DataMember]
public List<string> EnrolledCourses { get; set; }
public bool Equals(Student other)
{
if (!base.Equals(other))
{
return false;
}
if (this.EnrolledCourses == null && other.EnrolledCourses == null)
{
return true;
}
if ((this.EnrolledCourses == null && other.EnrolledCourses != null) ||
(this.EnrolledCourses != null && other.EnrolledCourses == null))
return false;
if (this.EnrolledCourses.Count != other.EnrolledCourses.Count)
return false;
for (int i = 0; i < this.EnrolledCourses.Count; i++)
{
if (this.EnrolledCourses[i] != other.EnrolledCourses[i])
return false;
}
return true;
}
}
}

Serializing object graph using MongoDB Bson serializer

I've been playing a little with the MongoDB Bson serializer, using the following piece of code:
class Program
{
public class myValue
{
public int Id = 0;
public string Label = "";
}
public class myValueMap : Dictionary<string, myValue>
{
}
public class myProdData
{
public myValueMap Mapping { get; set; }
}
public class mySystemPosition
{
public string Text { get; set; }
public myProdData ProdData { get; set; }
}
static void Main(string[] args)
{
BsonClassMap.RegisterClassMap<mySystemPosition>();
BsonClassMap.RegisterClassMap<myProdData>();
BsonClassMap.RegisterClassMap<myValueMap>();
BsonClassMap.RegisterClassMap<myValue>();
var o = new mySystemPosition()
{
ProdData = new myProdData()
{
Mapping = new myValueMap()
{
{"123", new myValue() {Id = 1, Label = "Item1"}},
{"345", new myValue() {Id = 2, Label = "Item2"}},
}
}
};
var bson = o.ToBson();
var text = Encoding.ASCII.GetString(bson);
}
}
however I don't seem to be able to get the myProdData.Mapping serialized....
Do I need to configure the MongoDB Bson serializer in a special way, to make this work?
You no need to use BsonClassMap.RegisterClassMap if you no need custom serializtion(documentation).
All your classes will be desirialzied according to default rules.
Also i am changed your example a little bit to get it work(i've replaces myValueMap class with Dictionary):
public class myProdData
{
public Dictionary<string, myValue> Mapping { get; set; }
}
static void Main(string[] args)
{
var o = new mySystemPosition()
{
ProdData = new myProdData()
{
Mapping = new Dictionary<string, myValue>()
{
{"123", new myValue() {Id = 1, Label = "Item1"}},
{"345", new myValue() {Id = 2, Label = "Item2"}},
}
}
};
var json = o.ToJson();
Console.WriteLine(json);
Console.ReadKey();
}
Here is console output(just well formatted):
{
"Text":null,
"ProdData":{
"Mapping":{
"123":{
"_id":1,
"Label":"Item1"
},
"345":{
"_id":2,
"Label":"Item2"
}
}
}
}
You can test your serializtion using ToJson() extention method, in order to view that all correct and after that use ToBson() if need.
The problem is that myValueMap derives from Dictionary. That results in a class that the AutoMap method can't handle.
I recommend you just use the Dictionary directly, as Andrew did in his reply.
Ufortunately the myValueMap is an object that I can't easily change, however it turns out, that's pretty easy to create your own (de)serializer....
public class myValueMapSerializer : IBsonSerializer
{
public object Deserialize(Bson.IO.BsonReader bsonReader, System.Type nominalType, System.Type actualType, IBsonSerializationOptions options)
{
if (nominalType != typeof(myValueMap)) throw new ArgumentException("Cannot serialize anything but myValueMap");
var res = new myValueMap();
var ser = new DictionarySerializer<string, myValue>();
var dic = (Dictionary<string, myValue>)ser.Deserialize(bsonReader, typeof(Dictionary<string, myValue>), options);
foreach (var item in dic)
{
res.Add(item.Key, item.Value);
}
return res;
}
public object Deserialize(Bson.IO.BsonReader bsonReader, System.Type nominalType, IBsonSerializationOptions options)
{
throw new Exception("Not implemented");
}
public bool GetDocumentId(object document, out object id, out IIdGenerator idGenerator)
{
id = null;
idGenerator = null;
return false;
}
public void Serialize(Bson.IO.BsonWriter bsonWriter, Type nominalType, object value, IBsonSerializationOptions options)
{
if (nominalType != typeof(myValueMap)) throw new ArgumentException("Cannot serialize anything but myValueMap");
var ser = new DictionarySerializer<string, myValue>();
ser.Serialize(bsonWriter, typeof(DictionarySerializer<string, myValue>), value, options);
}
public void SetDocumentId(object document, object id)
{
return;
}
}