Abp Framework global json converter cannot be configure - aspnetboilerplate

I would like to define custom json converter and replace with AbpStringToEnumConverter in abp framework. I am trying to change JsonSerializerOptions.Converters with below code but it's not working.
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
var hostingEnvironment = context.Services.GetHostingEnvironment();
ConfigureEnumCodeStringConverter();
...
}
private void ConfigureEnumCodeStringConverter()
{
Configure<AbpSystemTextJsonSerializerOptions>(options =>
{
var stringToEnumFactory = options.JsonSerializerOptions.Converters.Single(x => x.GetType() == typeof(AbpStringToEnumFactory));
options.JsonSerializerOptions.Converters.Remove(stringToEnumFactory);
options.JsonSerializerOptions.Converters.Add(new EnumToCodeFactory());
});
}
In addition, when i add converter attribute to requestDto property, thats working but i dont want to use attribute, i want to define global converter.
Example:
[Required]
[JsonConverter(typeof(EnumToCodeConverter<Gender>))]
public Gender Gender { get; set; }

AbpSystemTextJsonSerializerOptions works with custom converter IJSonSerializer in service layer, I tested and saw that. I need was to use converter in API(swagger), for this I realized that I had to configure the same in Mvc.JsonOptions and customer converter worked as I wanted.

Related

ASP NET Boilerplate, Login saved in ABPSession

I'm new on the asp net boilerplate framework, and i created a new mvc project multipage web application, without module zero.
I would like to use the AbpSession class that from what I understand has inside the user id that is taken over Thread.CurrentPrincipal.
However, I do not understand how to do after login, to save the user id in the Thread.CurrentPrincipal.
I've searched in the network and found several solutions, but in the AbpSession class the user id is always null.
The most optimal solution I found was this:
IList<Claim> claimCollection = new List<Claim>
{
new Claim(AbpClaimTypes.UserId, "5")
};
ClaimsIdentity claimsIdentity = new ClaimsIdentity(claimCollection);
var principal = new ClaimsPrincipal(claimsIdentity);
Thread.CurrentPrincipal = principal;
It's the first time I use principal and identity and despite being documented I did not quite understand how to use them with asp net boilerplate, and I did not find sample codes.
Do you know how to tell me the right way or tell me where to find some functional codes?
Thanks
Start expanding AbpSession
The last section has cleared the way of thinking. Let's roll up our sleeves and expand in this section.
AbpSession attributes have been injected into three base classes: Application Service, AbpController and ABP ApiController.
So we need to extend AbpSession at the domain level, which is the project at the end of. Core.
Now suppose we need to extend an Email attribute.
Extending IAbpSession
Locate the project at the end of. Core, add the Extensions folder, and then add the IAbpSession Extension interface inherited from IAbpSession:
namespace LearningMpaAbp.Extensions
{
public interface IAbpSessionExtension : IAbpSession
{
string Email { get; }
}
}
Implementing IAbpSession Extension
Add the AbpSession Extension class, which is based on Claims AbpSession and implements the IAbpSession Extension interface.
namespace LearningMpaAbp.Extensions
{
public class AbpSessionExtension : ClaimsAbpSession, IAbpSessionExtension, ITransientDependency
{
public AbpSessionExtension(
IPrincipalAccessor principalAccessor,
IMultiTenancyConfig multiTenancy,
ITenantResolver tenantResolver,
IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) :
base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
{}
public string Email => GetClaimValue(ClaimTypes.Email);
private string GetClaimValue(string claimType)
{
var claimsPrincipal = PrincipalAccessor.Principal;
var claim = claimsPrincipal?.Claims.FirstOrDefault(c => c.Type == claimType);
if (string.IsNullOrEmpty(claim?.Value))
return null;
return claim.Value;
}
}
}
UserClaimsPrincipalFactory.cs
//Override CreateAsync method to add your custom claim
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var claim = await base.CreateAsync(user);
claim.Identities.First().AddClaim(new Claim(ClaimTypes.Email, user.EmailAddress));
return claim;
}
Replace the injected AbbSession attribute
First replace the injected ABP Session in AbpController
Locate. ApplicationxxxControllerBase:AbpController. CS and inject IAbpSession Extension with attributes. Add the following code:
//AbpSession Hiding Parent Class
public new IAbpSessionExtension AbpSession { get; set; }
Replace the injected ABP Session in Application Service
Locate. ApplicationxxxAppServiceBase.cs. Introduce IAbpSession Extension with attributes, and add the following code as well:
//AbpSession Hiding Parent Class
public new IAbpSessionExtension AbpSession { get; set; }
Chaneg the injected ABP Session in Views AbpRazorPage
Locate. ApplicationxxxRazorPage.cs. Introduce IAbpSession Extension with attributes, and add the following code as well:
[RazorInject]
public IAbpSessionExtension AbpSession { get; set; }
Altough the question is very general, i would like to share you some code about how to add custom field to AbpSession in ASP.NET Core.
MyAppSession.cs
//Define your own session and add your custom field to it
//Then, you can inject MyAppSession and use it's new property in your project.
public class MyAppSession : ClaimsAbpSession, ITransientDependency
{
public MyAppSession(
IPrincipalAccessor principalAccessor,
IMultiTenancyConfig multiTenancy,
ITenantResolver tenantResolver,
IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) :
base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
{
}
public string UserEmail
{
get
{
var userEmailClaim = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == "Application_UserEmail");
if (string.IsNullOrEmpty(userEmailClaim?.Value))
{
return null;
}
return userEmailClaim.Value;
}
}
}
UserClaimsPrincipalFactory.cs
//Override CreateAsync method to add your custom claim
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
var claim = await base.CreateAsync(user);
claim.Identities.First().AddClaim(new Claim("Application_UserEmail", user.EmailAddress));
return claim;
}

DbGeometry serialization issue in Asp.net web api

I'm building an OData v3 Web API with Entity Framework 6.0 Code First.
Everything works well and I can execute CRUD operations back to the api server.
However I'm using Spatial Types and some of my entities have DbGeometry properties. When I try to update/post an entity with a DbGeometry type from a client application (just a console application for tests) I get this DataServiceRequestException:
No parameterless constructor defined for this object.
It took me a while but I identified the DbGeometry type as the responsible. I already looked at this topic here and made a custom JsonConverter, where I applied to the property:
[Required]
[JsonConverter(typeof(DbGeometryConverter))]
[Column("geometria")]
public DbGeometry Geometria { get; set; }
That didn't worked. The object is not deserialized on the web api server unless I remove the DbGeometry property.
I also tried to change the Global json serializer behavior
var formatters = GlobalConfiguration.Configuration.Formatters;
var jsonFormatter = formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new DbGeometryConverter());
Also futile. I really need the DbGeometry properties. What else can I do to work around this issue?
A little late, but for those who'll seek an answer:
I've managed to do it with the exact same code on a controller level. The idea was taken from this SO Question&Answer.
So, here is the code including the DbGeometryConverter.
DbGeometryConverter.cs:
public class DbGeometryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType.IsAssignableFrom(typeof(DbGeometry));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var location = JObject.Load(reader);
var token = location["Geometry"]["WellKnownText"];
string geom = token.ToString();
token = location["Geometry"]["CoordinateSystemId"];
int srid = token != null ? int.Parse(token.ToString()) : 0;
var converted = DbGeometry.FromText(geom, srid);
return converted;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite => false;
}
CustomJsonAttribute.cs:
public class CustomJsonAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
var formatter = controllerSettings.Formatters.JsonFormatter;
formatter.SerializerSettings.Converters.Add(new DbGeometryConverter());
}
}
And [CustomJson] attribute on a controller that uses DbGeometry.

ASP.NET MVC enable custom validation attributes

I'm attempting to create my own model validation attributes for an ASP.NET MVC project. I've followed the advice from this question but cannot see how to get #Html.EditorFor() to recognise my custom attributes. Do I need to register my custom attribute classes in the web.config somewhere? A comment on this this answer seems to be asking the same thing.
FYI the reason I'm creating my own attributes is because I want to retrieve the field display names and validation messages from Sitecore and don't really want to go down the route of creating a class with a ton of static methods to represent each text property, which is what I'd have to do if I were to use
public class MyModel
{
[DisplayName("Some Property")]
[Required(ErrorMessageResourceName="SomeProperty_Required", ErrorMessageResourceType=typeof(MyResourceClass))]
public string SomeProperty{ get; set; }
}
public class MyResourceClass
{
public static string SomeProperty_Required
{
get { // extract field from sitecore item }
}
//for each new field validator, I would need to add an additional
//property to retrieve the corresponding validation message
}
This question has been answered here:
How to create custom validation attribute for MVC
In order to get your custom validator attribute to work, you need to register it. This can be done in Global.asax with the following code:
public void Application_Start()
{
System.Web.Mvc.DataAnnotationsModelValidatorProvider.RegisterAdapter(
typeof (MyNamespace.RequiredAttribute),
typeof (System.Web.Mvc.RequiredAttributeAdapter));
}
(If you're using WebActivator you can put the above code into a startup class in your App_Start folder.)
My custom attribute class looks like this:
public class RequiredAttribute : System.ComponentModel.DataAnnotations.RequiredAttribute
{
private string _propertyName;
public RequiredAttribute([CallerMemberName] string propertyName = null)
{
_propertyName = propertyName;
}
public string PropertyName
{
get { return _propertyName; }
}
private string GetErrorMessage()
{
// Get appropriate error message from Sitecore here.
// This could be of the form "Please specify the {0} field"
// where '{0}' gets replaced with the display name for the model field.
}
public override string FormatErrorMessage(string name)
{
//note that the display name for the field is passed to the 'name' argument
return string.Format(GetErrorMessage(), name);
}
}

Use named registration in autofac with MVC Filter Attribute Property Injection

I'm using Autofac to fill in public properties of my filters, according to https://code.google.com/p/autofac/wiki/Mvc3Integration#Filter_Attribute_Property_Injection and it worked great.
Until I tried to use a named registration for one of the dependencies. I cannot find a way to do it. I tried to manually register my filters like so:
builder.RegisterType<MyCustomAttribute>()
.WithProperty(ResolvedParameter.ForNamed<INamedDependency>("dependencyName"));
before calling the RegisterFilterProvider method, but that didn't work.
Any ideas? In case this has been fixed in a newer version, I'm using version 2.5.2.830.
Thanks,
Kostas
May be you just forgot to register INamedDependency instance in your container:
public class MyCustomAttribute : FilterAttribute
{
public IDependencyName DependencyName { get; set; }
}
public interface IDependencyName
{
}
public class DependencyName : IDependencyName
{
}
[Test]
public void ResolveCustomTest()
{
// Arrange
var dependencyInstance = new DependencyName();
var builder = new ContainerBuilder();
builder.RegisterInstance(dependencyInstance).Named<IDependencyName>("dependencyName");
builder.RegisterType<MyCustomAttribute>().WithProperty(ResolvedParameter.ForNamed<IDependencyName>("dependencyName"));
builder.RegisterFilterProvider();
var root = builder.Build();
// Act
var attr = root.BeginLifetimeScope("AutofacWebRequest").Resolve<MyCustomAttribute>();
// Assert
Assert.AreEqual(attr.DependencyName, dependencyInstance);
}

asp.net mvc 3 run validations on a model

How can i run all DataAnnotation validations on model?
I'm building a model instance from code and i don't have no modelstate binding or anything. I just want to run all my validations against it... I am using EF CodeFirst.
public class Category
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
cat = new Category();
if (cat.IsValid()) { /* blah */ } // i want something like this
I know it's probably a stupid question but i can't seem to find an answer anywhere..
This is similar to this question about unit testing data annotations. You could add an extension method similar to this:
public static class ValidationExtension {
public static bool IsValid<T>(this T model) where T: class {
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(model, null, null);
Validator.TryValidateObject(model, validationContext, validationResults, true);
return validationResults.Count == 0;
}
}
The title of this question includes ASP.net MVC.
Please be aware that the Validator class and MVCs validation has subtle differences.
For example:
DataAnnotations.Validator doesn't support buddy class out of box.
MVC can be configured to use another validation framework for example FluentValidation.
If you want to run MVC's validation and populate the ModelState, you can call the TryValidateModel or ValidateModel.
if you don't want to populate the ModelState, use this code snippet in your controller.
var metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
ModelValidator.GetModelValidator(metadata, ControllerContext).Validate(null);

Resources