I'm trying to implement authorization as Seroter described here (Service Authorization section). I've GAC'ed the library, changed machine.config and able to pick the custom behavior in Select Behavior Extension dialog. But I can't set the 'WindowsGroup' value, it gives me "Object reference not set to an instance of an object" and I can't figure why. Did anybody implement service authorization?
Finally solved this problem.
using System;
using System.Configuration;
using System.ServiceModel.Configuration;
namespace Esb.Service.Authorization
{
public class EsbBehaviorElement : BehaviorExtensionElement
{
private const string _windowsgroupIndexName = "windowsgroup";
public EsbBehaviorElement()
{
if (!base.Properties.Contains(_windowsgroupIndexName))
{
base.Properties.Add(new ConfigurationProperty(_windowsgroupIndexName, typeof(string)));
}
}
[ConfigurationProperty("WindowsGroup", IsRequired = false, DefaultValue = "")]
public string WindowsGroup
{
get
{
return (string)base[_windowsgroupIndexName];
}
set
{
base[_windowsgroupIndexName] = value;
}
}
public override Type BehaviorType
{
get
{
return typeof(EsbServiceBehavior);
}
}
protected override object CreateBehavior()
{
return new EsbServiceBehavior(WindowsGroup);
}
}
}
I don't know why Seroter's solution works without ctor where one should add "windowsgroup" property to the base collection of properties.
Related
I'm working on an inherited ASP.NET MVC 4 project using .net framework 4.5.
We've added a new configuration section files and relevant class files and from what we can tell (docs.Microsoft and other online guides) it's set up correctly.
The Problem
ConfigurationManager.GetSection() returns null.
According to the docs this returns null if the section doesn't exist. Troubleshooting this has been troublesome.
The Code
The website is an ASP.NET Web Application. Properties window sets assembly name to Client.Project.UI.Base (which is the DLL in the published bin). This is the assembly name used for the config types FQN and assembly in web.config.
NB: the config section SupportCaseConfiguration was originally in a separate file and the SupportTickets section just specified the configSource. This has been moved into the web.config to reduce the number of potential issues while troubleshooting.
web.config:
<configSections>
<!-- define type for new section -->
<section name="SupportTickets" type="Client.Project.UI.Base.Infrastructure.Services.SupportCaseConfigurationSection, Client.Project.UI.Base"/>
</configSections>
<!-- new config section -->
<SupportTickets>
<SupportCaseConfiguration>
<caseTypes>
<add name="tenant.TestCase" label="Test Case" recipient="email_here" ccList="" bccList="" />
</caseTypes>
</SupportCaseConfiguration>
</SupportTickets>
SupportCaseConfiguration.cs:
namespace Client.Project.UI.Base.Infrastructure.Services
{
using System.Configuration;
//Extend the ConfigurationSection class.
public class SupportCaseConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("caseTypes", IsDefaultCollection = true)]
public CaseTypeElementCollection CaseTypes
{
get { return (CaseTypeElementCollection)this["caseTypes"]; }
}
}
//Extend the ConfigurationElementCollection class.
[ConfigurationCollection(typeof(CaseTypeElement))]
public class CaseTypeElementCollection : ConfigurationElementCollection
{
public CaseTypeElement this[int index]
{
get { return (CaseTypeElement)BaseGet(index); }
set
{
if (BaseGet(index) != null)
BaseRemoveAt(index);
BaseAdd(index, value);
}
}
protected override ConfigurationElement CreateNewElement()
{
return new CaseTypeElement();
}
protected override object GetElementKey(ConfigurationElement element)
{
return ((CaseTypeElement)element).Name;
}
}
//Extend the ConfigurationElement class. This class represents a single element in the collection.
public class CaseTypeElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true)]
public string Name
{
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("label", IsRequired = true)]
public string Label
{
get { return (string)this["label"]; }
set { this["label"] = value; }
}
[ConfigurationProperty("recipient", IsRequired = true)]
public string Recipient
{
get { return (string)this["recipient"]; }
set { this["recipient"] = value; }
}
[ConfigurationProperty("ccList", IsRequired = true)]
public string CcList
{
get { return (string)this["ccList"]; }
set { this["ccList"] = value; }
}
[ConfigurationProperty("bccList", IsRequired = true)]
public string BccList
{
get { return (string)this["bccList"]; }
set { this["bccList"] = value; }
}
}
}
Elsewhere, getting new config data:
SupportCaseConfigurationSection supportTicketsConfigurationSection = ConfigurationManager.GetSection("SupportCaseConfiguration") as SupportCaseConfigurationSection;
The site is being published locally, I can attach a debugger to ensure the latest versions of files are being used. I can see the config section in the published web.config.
I've been looking at this I can no longer see if anything is amiss. It all looks fine for me...
Any ideas, troubleshooting tips or even pointing out I'm being a muppet would be useful.
Cheers.
I can only assume the issue was something to do with the config classes in the site assembly.
Even after trying online examples, copypasting into the project, not even they worked.
As soon as I put the configuration section/element classes in a separate project (moved out of the website project) it started working.
In experimenting with Service Fabric remoting I have some data types that are not serialized correctly. This is causing me many issues.
From the documentation it appears that everything needs to be decorated with [DataContract]. After using this on some test types it does appear that they serialize correctly.
However I frankly don't want to have to decorate everything. That would be a huge step backwards for me. I would prefer to use custom serialization all the way around.
This documentation seems to suggest that it is possible to register a custom serializer however it appears to only be for stateful services. I am primarily using remoting with stateless services.
The current remoting stack requires that your types use DataContract. Supposedly the team is close to releasing a new remoting stack in the near future that contains the ability to plug in custom serialization and a lot of improvements on the performance side but this is not available yet.
In the meantime, a workaround (not a very nice one mind you) is to make all of your proxies receive string or byte[] or something like that and take care of serialization/deserialization manually using something like JSON.Net. Personally I'd bite the bullet and make your types Data Contract Serializable until the new remoting bits are available.
With the release of Service Fabric V2 Remoting, this is now possible. See here for further details. Below
Here is an implementation of MessagePack remoting serializer I have used, but in your case the JSON example in the docs would probably suffice.
public class MessagePackMessageFactory : IServiceRemotingMessageBodyFactory
{
public IServiceRemotingRequestMessageBody CreateRequest(string interfaceName, string methodName, int numberOfParameters)
{
return new MessagePackRemotingRequestMessageBody(numberOfParameters);
}
public IServiceRemotingResponseMessageBody CreateResponse(string interfaceName, string methodName)
{
return new MessagePackServiceRemotingResponseMessageBody();
}
}
[MessagePackObject]
public class MessagePackRemotingRequestMessageBody : IServiceRemotingRequestMessageBody
{
[Key(0)]
public object Value;
public MessagePackRemotingRequestMessageBody()
{
}
public MessagePackRemotingRequestMessageBody(int parameterInfos)
{
}
public void SetParameter(int position, string paramName, object parameter)
{
Value = parameter;
}
public object GetParameter(int position, string paramName, Type paramType)
{
return Value;
}
}
[MessagePackObject]
public class MessagePackServiceRemotingResponseMessageBody : IServiceRemotingResponseMessageBody
{
[Key(0)]
public object Response;
public object Get(Type paramType)
{
// ignore paramType?
return Response;
}
public void Set(object response)
{
Response = response;
}
}
public class ServiceRemotingResponseMessagePackMessageBodySerializer : IServiceRemotingResponseMessageBodySerializer
{
public OutgoingMessageBody Serialize(IServiceRemotingResponseMessageBody responseMessageBody)
{
if (!(responseMessageBody is MessagePackServiceRemotingResponseMessageBody body))
{
return new OutgoingMessageBody(new[] { new ArraySegment<byte>(new byte[0]) });
}
var bytes = MessagePackSerializer.Serialize(body, ServiceFabricResolver.Instance);
return new OutgoingMessageBody(new[] { new ArraySegment<byte>(bytes) });
}
public IServiceRemotingResponseMessageBody Deserialize(IncomingMessageBody messageBody)
{
using (var stream = messageBody.GetReceivedBuffer())
{
if (stream.Length == 0)
{
return new MessagePackServiceRemotingResponseMessageBody();
}
var body = MessagePackSerializer.Deserialize<MessagePackServiceRemotingResponseMessageBody>(stream, ServiceFabricResolver.Instance);
return body;
}
}
}
public class ServiceRemotingMessagePackSerializationProvider : IServiceRemotingMessageSerializationProvider
{
public IServiceRemotingRequestMessageBodySerializer CreateRequestMessageSerializer(Type serviceInterfaceType,
IEnumerable<Type> requestBodyTypes)
{
return new ServiceRemotingRequestMessagePackMessageBodySerializer();
}
public IServiceRemotingResponseMessageBodySerializer CreateResponseMessageSerializer(Type serviceInterfaceType, IEnumerable<Type> responseBodyTypes)
{
return new ServiceRemotingResponseMessagePackMessageBodySerializer();
}
public IServiceRemotingMessageBodyFactory CreateMessageBodyFactory()
{
return new MessagePackMessageFactory();
}
}
public class ServiceRemotingRequestMessagePackMessageBodySerializer : IServiceRemotingRequestMessageBodySerializer
{
public OutgoingMessageBody Serialize(IServiceRemotingRequestMessageBody serviceRemotingRequestMessageBody)
{
if (serviceRemotingRequestMessageBody == null) return null;
if (!(serviceRemotingRequestMessageBody is MessagePackRemotingRequestMessageBody body))
{
return new OutgoingMessageBody(new[] { new ArraySegment<byte>(new byte[0]) });
}
var bytes = MessagePackSerializer.Serialize(body, ServiceFabricResolver.Instance);
return new OutgoingMessageBody(new[] { new ArraySegment<byte>(bytes) });
}
public IServiceRemotingRequestMessageBody Deserialize(IncomingMessageBody messageBody)
{
using (var stream = messageBody.GetReceivedBuffer())
{
if (stream.Length == 0)
{
return new MessagePackRemotingRequestMessageBody();
}
var body = MessagePackSerializer.Deserialize<MessagePackRemotingRequestMessageBody>(stream, ServiceFabricResolver.Instance);
return body;
}
}
}
In ASP.Net MVC 5, custom data annotation validator can be implemented by inheriting DataAnnotationsModelValidator and registering using DataAnnotationsModelValidatorProvider.RegisterAdapter(...). In ASP.Net Core MVC, how can I achieve this?
I found similar question at ASP.net core MVC 6 Data Annotations separation of concerns, but can anyone show me simple example code?
It seems to me ASP.NET Core MVC does not have support for DataAnnotationsModelValidatorProvider.RegisterAdapter anymore. The solution I discovered is as follows:
Suppose I want to change the Validator for RequiredAttribute to my own validator adaptor (MyRequiredAttributeAdaptor), Change the default error message of EmailAddressAttribute, and change the Localized Error Message Source for 'CompareAttribute' to my own message.
1- Create a custom ValidationAttributeAdapterProvider
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.Extensions.Localization;
using System.ComponentModel.DataAnnotations;
public class CustomValidationAttributeAdapterProvider
: ValidationAttributeAdapterProvider, IValidationAttributeAdapterProvider
{
public CustomValidationAttributeAdapterProvider() { }
IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(
ValidationAttribute attribute,
IStringLocalizer stringLocalizer)
{
IAttributeAdapter adapter;
if (attribute is RequiredAttribute)
{
adapter = new MyRequiredAttributeAdaptor((RequiredAttribute) attribute, stringLocalizer);
}
else if (attribute is EmailAddressAttribute)
{
attribute.ErrorMessage = "Invalid Email Address.";
adapter = base.GetAttributeAdapter(attribute, stringLocalizer);
}
else if (attribute is CompareAttribute)
{
attribute.ErrorMessageResourceName = "InvalidCompare";
attribute.ErrorMessageResourceType = typeof(Resources.ValidationMessages);
var theNewattribute = attribute as CompareAttribute;
adapter = new CompareAttributeAdapter(theNewattribute, stringLocalizer);
}
else
{
adapter = base.GetAttributeAdapter(attribute, stringLocalizer);
}
return adapter;
}
}
2- Add the CustomValidationAttributeAdapterProvider to start up:
Add the following line to public void ConfigureServices(IServiceCollection services) in Startup.cs:
services.AddSingleton <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider> ();
Here is MyRequiredAttributeAdaptor adaptor:
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
public class MyRequiredAttributeAdaptor : AttributeAdapterBase<RequiredAttribute>
{
public MyRequiredAttributeAdaptor(RequiredAttribute attribute, IStringLocalizer stringLocalizer)
: base(attribute, stringLocalizer)
{
}
public override void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
MergeAttribute(context.Attributes, "data-val-required", GetErrorMessage(context));
}
/// <inheritdoc />
public override string GetErrorMessage(ModelValidationContextBase validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException(nameof(validationContext));
}
return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
}
}
References:
1- See the example of Microsoft: Entropy project: This is a great sample for diffrent features of .NET Core. In this question: see the MinLengthSixAttribute implementation in the Mvc.LocalizationSample.Web sample:
https://github.com/aspnet/Entropy/tree/dev/samples/Mvc.LocalizationSample.Web
2- In order to see how the attribute adapters works see asp.Microsoft.AspNetCore.Mvc.DataAnnotations on github:
https://github.com/aspnet/Mvc/tree/master/src/Microsoft.AspNetCore.Mvc.DataAnnotations
To define a custom validator by a annotation you can define your own class that derives from ValidationAttribute and override the IsValid method. There is no need to register this class explicitly.
In this example a custom validation attribute is used to accept only odd numbers as valid values.
public class MyModel
{
[OddNumber]
public int Number { get; set; }
}
public class OddNumberAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
try
{
var number = (int) value;
if (number % 2 == 1)
return ValidationResult.Success;
else
return new ValidationResult("Only odd numbers are valid.");
}
catch (Exception)
{
return new ValidationResult("Not a number.");
}
}
}
A second approach is that the Model class implements IValidatableObject. This is especially useful, if validation requires access to multiple members of the model class. Here is the second version of the odd number validator:
public class MyModel : IValidatableObject
{
public int Number { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Number % 2 == 0)
yield return new ValidationResult(
"Only odd numbers are valid.",
new [] {"Number"});
}
}
You can find more information about custom validation in https://docs.asp.net/en/latest/mvc/models/validation.html#custom-validation.
I want to derive an attribute from System.Web.Http.AuthorizeAttribute as follows:
using System.Web.Http.Controllers;
using WebApi = System.Web.Http;
namespace Memzuc.Net.Authorization {
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : WebApi.AuthorizeAttribute {
private static Logger logger = LogManager.GetCurrentClassLogger();
public AuthorizeAttribute() : base() {
logger.Debug("Memzuc.Net.Authorization.AuthorizeAttribute ctor");
}
protected override bool IsAuthorized(HttpActionContext actionContext) {
logger.Debug("Memzuc.Net.Authorization.AuthorizeAttribute.IsAuthorized called");
return base.IsAuthorized(actionContext);
}
public override void OnAuthorization(HttpActionContext actionContext) {
logger.Debug("Memzuc.Net.Authorization.AuthorizeAttribute.OnAuthorization called");
base.OnAuthorization(actionContext);
}
}
}
Sure I shall do some other useful things! And I use this attribute like so:
using System.Web.Http;
using MemAuth = Memzuc.Net.Authorization;
namespace Memzuc.Net.Controllers {
[RoutePrefix("main-risk")]
public class MainRiskController : ApiController {
[Route("")]
[MemAuth.Authorize]
public IEnumerable<MainRisk> Get() {
var repo = GetMainRiskRepo();
return repo.GetMainRiskList();
}
I see ctor records in the log when the application begins. But both IsAuhorized() and OnAuthorization() are not get called and MainRiskController.Get() method is entered without any authorization control.
Am I doing something wrong? Or is it necessary to register the new authorization attribute to somewhere?
I found the problem. The attribute and the controller that use it resides in different assemblies, which are referencing different versions of System.Web.Http. As the same version referenced the attribute worked.
Given this XML configuration (which works)
<component type="X.Y.Z.ActivityService, X.Y.Z.Services" id="X.Y.Z.ActivityService" lifestyle="transient">
<parameters>
<Listeners>
<array>
<item>${DefaultActivityListener}</item>
</array>
</Listeners>
</parameters>
</component>
<component type="X.Y.Z.DefaultActivityListener, X.Y.Z.Services" id="DefaultActivityListener" lifestyle="transient" />
I have converted to use the fluent API as below (which doesn't work):
Container.Register(
Component.For<X.Y.Z.ActivityService>()
.ServiceOverrides(
ServiceOverride.ForKey("Listeners").Eq(typeof(X.Y.Z.DefaultActivityListener).Name))
.LifeStyle.Transient
);
Container.Register(
Component.For<X.Y.Z.DefaultActivityListener>()
.Named("DefaultActivityListener")
.LifeStyle.Transient
);
When I now attempt to resolve an instance of X.Y.Z.ActivityService Windsor throws a NotImplementedException in Castle.MicroKernel.SubSystems.Conversion.ArrayConverter.PerformConversion(String, Type).
The implementation of the PerformConversion method is:
public override object PerformConversion(String value, Type targetType)
{
throw new NotImplementedException();
}
I should add that if I remove the ServiceOverrides call, all behaves as expected. So there is specifically something wrong in the way I am wiring up the Listeners parameter. Listeners by the way is a property as opposed to a constructor parameter.
Seeing as the XML config works as expected how do I best use the fluent API (short of implementing the PerformConversion method) in order to achieve the same result?
I am using Release 2.0.
EDIT
I will extend the question to how would you achieve this configuration in code, with or without use of the fluent API.
UPDATE
It appears the problem occurs if you attempt to assign a single element to an array property. Unit tests provided below to illustrate issue.
namespace Components
{
public class A
{
public I[] I { get; set; }
}
public interface I
{
string Name { get; }
}
public class B : I
{
public string Name { get { return "B"; } }
}
public class C : I
{
public string Name { get { return "C"; } }
}
}
[TestMethod]
public void ArrayPropertyTestApi()
{
//PASSES
using (Castle.Windsor.WindsorContainer container = new Castle.Windsor.WindsorContainer())
{
container.Register(Component.For<Components.A>().ServiceOverrides(ServiceOverride.ForKey("I").Eq(typeof(Components.B).FullName, typeof(Components.C).FullName)));
container.Register(Component.For<Components.B>());
container.Register(Component.For<Components.C>());
Components.A svc = container.Resolve<Components.A>();
Assert.IsTrue(svc.I.Length == 2);
Assert.IsTrue(svc.I[0].Name == "B");
Assert.IsTrue(svc.I[1].Name == "C");
}
}
[TestMethod]
public void ArrayPropertyTestApi2()
{
//FAILS
using (Castle.Windsor.WindsorContainer container = new Castle.Windsor.WindsorContainer())
{
container.Register(Component.For<Components.A>().ServiceOverrides(ServiceOverride.ForKey("I").Eq(typeof(Components.B).FullName)));
container.Register(Component.For<Components.B>());
container.Register(Component.For<Components.C>());
Components.A svc = container.Resolve<Components.A>(); //Throws NotImplementedException
Assert.IsTrue(svc.I.Length == 1);
Assert.IsTrue(svc.I[0].Name == "B");
}
}
Question still stands.
Thanks.
[TestFixture]
public class WindsorTests {
[Test]
public void ArrayConfig() {
var container = new WindsorContainer();
container.Register(Component.For<Listener>().Named("listener"));
container.Register(Component.For<ActivityService>()
.ServiceOverrides(ServiceOverride.ForKey("listeners").Eq(new[] {"listener"})));
var service = container.Resolve<ActivityService>();
Assert.AreEqual(1, service.Listeners.Length);
}
}
public class Listener {}
public class ActivityService {
public Listener[] Listeners { get; set; }
}
The key part here is the new[] {"listener"}. The MicroKernel needs to know that the parameter listeners is an array, if you pass just "listener" it assumes that the parameter is scalar and throws because it can't convert a scalar to an array.