Let me explain the scenario: when I hardcode the Domainname\Usergroup, the permission levels are working perfectly.
But what I want to do is instead of hardcoding the Domain\Usergroup in the code, I want to get it from the config file:
string xyz = ConfigurationManager.Appsettings["DomainKeyfromConfig"]
[PrincipalPermission(SecurityAction.Demand,Role = #xyz)]
public Response<String> GetString(String request)
{
// some code
}
When I'm trying to do this, I get this error
CS0182:An attribute argument must be a constant expression, typeof
expression or array creation expression of an attribute parameter type
Your screenshot has shown the specific reason for the error.You can't do that with attributes, they have to be constants as stated in the error message. If you wanted to get a value from the configuration file, you could do it by passing the key to the attribute, and then in the constructor get the value you want from the configurationmanager.https://stackoverflow.com/questions/31481820/an-attribute-argument-must-be-a-constant-expression-typeof-expression-or-array
public MyAttribute :Attribute
{
private string _config;
public MyAttribute(string configKey)
{
_config = ConfigurationManager.AppSettings[configKey];
...
}
}
Related
I am writing a source generator but am struggling to get the value of an argument passed to the constructor of my attribute.
I inject the following into the compilation:
namespace Richiban.Cmdr
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CmdrMethod : System.Attribute
{
private readonly string _alias;
public CmdrMethod(string alias)
{
_alias = alias;
}
}
}
And then in my sample application I have the following:
public static class InnerContainerClass
{
[CmdrMethod("test")]
public static void AnotherMethod(Data data)
{
Console.WriteLine($"In {nameof(AnotherMethod)}, {new { data }}");
}
}
This compiles without errors or warnings and I am successfully able to find all methods that have been decorated with my CmdrMethod attribute, but I am unable to get the value passed to the attribute because, for some reason, the ConstructorArguments property of the attribute is empty:
private static ImmutableArray<string> GetAttributeArguments(
IMethodSymbol methodSymbol,
string attributeName)
{
var attr = methodSymbol
.GetAttributes()
.Single(a => a.AttributeClass?.Name == attributeName);
var arguments = attr.ConstructorArguments;
if (methodSymbol.Name == "AnotherMethod")
Debugger.Launch();
return arguments.Select(a => a.ToString()).ToImmutableArray();
}
Have I misunderstood this API? What am I doing wrong?
The Compilation is immutable. When you add the source code for your attribute, you still have a Compilation object that don't know anything about the attribute. This causes the AttributeClass to be an ErrorType as #jason-malinowski mentioned. This is very known for this kind of source generators, and the solution is just simple. Create a new Compilation with the symbol you injected, then get a SemanticModel from the new Compilation:
// You should already have something similar to the following two lines.
SourceText attributeSourceText = SourceText.From("CmdrMethod source code here", Encoding.UTF8);
context.AddSource("CmdrMethod.g.cs" /* or whatever name you chose*/, attributeSourceText);
// This is the fix.
ParseOptions options = ((CSharpCompilation)context.Compilation).SyntaxTrees[0].Options;
SyntaxTree attributeTree = CSharpSyntaxTree.ParseText(atttributeSourceText, (CSharpParseOptions)options);
Compilation newCompilation = context.Compilation.AddSyntaxTrees(attributeTree);
// Get the semantic model from 'newCompilation'. It should have the information you need.
UPDATE: The experience became better starting from Microsoft.CodeAnalysis 3.9 packages, you can now add the attribute in Initialize instead of Execute:
public void Initialize(GeneratorInitializationContext context)
{
// Register the attribute source
context.RegisterForPostInitialization((i) => i.AddSource("CmdrMethod.g.cs", attributeText));
// .....
}
Then, you can directly work with the compilation you get in Execute.
See AutoNotify sample.
The AttributeClass property is an ErrorType, which means that the compiler didn't actually know what that type was. We fill in a fake "error" type when we had a name for the type but otherwise didn't know what it was; this makes some downstream handling easier for certain features. In any case, figure out where that's coming from. You commented that "This compiles without errors or warnings" but whatever environment your Roslyn code is running in, you likely have a compilation that will give errors.
I'm executing the URL
https://localhost:44310/api/Licensee/{"name":"stan"}
in address field of my browser, getting the error
"title": "Unsupported Media Type", "status": 415
which is described as
... the origin server is refusing to service the request because the payload
is in a format not supported by this method on the target resource.
The suggested troubleshot is
... due to the request's indicated Content-Type or Content-Encoding, or as a result of inspecting the data ...
I can't really control what header the browser provides. Due to intended usage, I can't rely on Postman or a web application. It needs to be exected from the URL line. The parameter will differ in structure, depending what search criteria that are applied.
The controller looks like this.
[HttpGet("{parameters}")]
public async Task<ActionResult> GetLicensee(LicenseeParameters parameters)
{
return Ok(await Licensee.GetLicenseeByParameters(parameters));
}
I considered decorating the controller with [Consumes("application/json")] but found something dicouraging it. I tried to add JSON converter as suggested here and here but couldn't really work out what option to set, fumbling according to this, not sure if I'm barking up the right tree to begin with.
services.AddControllers()
.AddJsonOptions(_ =>
{
_.JsonSerializerOptions.AllowTrailingCommas = true;
_.JsonSerializerOptions.PropertyNamingPolicy = null;
_.JsonSerializerOptions.DictionaryKeyPolicy = null;
_.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
});
My backup option is to use query string specifying the desired options for a particular search. However, I'd prefer to use the object with parameters for now.
How can I resolve this (or at least troubleshoot further)?
The reason is that there might be a loooot of parameters and I don't want to refactor the controller's signature each time
Actually, you don't have to change the controller's signature each time. ASP.NET Core Model binder is able to bind an object from query string automatically. For example, assume you have a simple controller:
[HttpGet("/api/licensee")]
public IActionResult GetLicensee([FromQuery]LicenseeParameters parameters)
{
return Json(parameters);
}
The first time the DTO is:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
}
What you need is to send a HTTP Request as below:
GET /api/licensee?name=stan¬e=it+works
And later you decide to change the LicenseeParameters:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
public List<SubNode> Children{get;set;} // a complex array
}
You don't have to change the controller signature. Just send a payload in this way:
GET /api/licensee?name=stan¬e=it+works&children[0].nodeName=it&children[1].nodeName=minus
The conversion is : . represents property and [] represents collection or dictionary.
In case you do want to send a json string within URL, what you need is to create a custom model binder.
internal class LicenseeParametersModelBinder : IModelBinder
{
private readonly JsonSerializerOptions _jsonOpts;
public LicenseeParametersModelBinder(IOptions<JsonSerializerOptions> jsonOpts)
{
this._jsonOpts = jsonOpts.Value;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var name= bindingContext.FieldName;
var type = bindingContext.ModelType;
try{
var json= bindingContext.ValueProvider.GetValue(name).FirstValue;
var obj = JsonSerializer.Deserialize(json,type, _jsonOpts);
bindingContext.Result = ModelBindingResult.Success(obj);
}
catch (JsonException ex){
bindingContext.ModelState.AddModelError(name,$"{ex.Message}");
}
return Task.CompletedTask;
}
}
and register the model binder as below:
[HttpGet("/api/licensee/{parameters}")]
public IActionResult GetLicensee2([ModelBinder(typeof(LicenseeParametersModelBinder))]LicenseeParameters parameters)
{
return Json(parameters);
}
Finally, you can send a json within URL(suppose the property name is case insensive):
GET /api/licensee/{"name":"stan","note":"it works","children":[{"nodeName":"it"},{"nodeName":"minus"}]}
The above two approaches both works for me. But personally I would suggest you use the the first one as it is a built-in feature.
I have a custom attribute extension of System.Web.Http.Filters.ActionFilterAttribute that I am using for logging with Web API controllers. I am experiencing an issue that indicates that the attribute object is being reused call to call. Data in my public properties from an initial call will appear in the logged information for a subsequent call and so on.
I read in this post that I "should never store instance state in an action filter that will be reused between the different methods." He goes on to say,"This basically means that the same instance of the action filter can be reused for different actions and if you have stored instance state in it it will probably break."
My custom attribute is apparently "break" ing. Thus began my search to answer the question …
How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?
An example is given in the post I referenced above of how data should be passed method to method using the HttpContext.Items dictionary. That's great and I can see how that would help but I'm not using ASP.net MVC'sSystem.Web.Http.Mvc.ActionFilterAttribute – which the poster uses in his answer. I'm doing Web API and the context object passed into the OnActionExecuting method is of type HttpActionContext and not of type ActionExecutingContext. I do not have access to the HttpContext.Items dictionary through the passed context, however, I believe that it is safe to access the HttpContext like this:
HttpContext.Current.Items[key]
Is that safe?
I do not have access to that dictionary in the constructor however and since that is where I receive my parameterized message string as a positional parameter, I am seemingly dependent on stored instance state.
So what to do?
In this post – also dependent on ASP.net MVC's System.Web.Http.Mvc.ActionFilterAttributeand its ActionExecutingContext– the poster uses the ActionParameters property of that context object to get at the parameters passed to the attribute, but I cannot find any equivalent in Web API's HttpActionContext. If I could, this would seem to be the answer! But alas…
How can I safely get to the positional parameter value passed into my constructor and the named parameter value passed in through a public property within the OnActionExecuting method?
Posts I have researched:
Are ActionFilterAttributes reused across threads? How does that work?
MVC Action Filter and Multiple Threads
passing action method parameter to ActionFilterAttribute in asp.net mvc
Why is my ASP.NET Web API ActionFilterAttribute OnActionExecuting not firing?
System.Web.Mvc.ActionFilterAttribute vs System.Web.Http.Filters.ActionFilterAttribute
Web Api 2 HttpContext or HttpActionContext
Background: In the constructor, I pass a parameterized message string that includes placeholders for the values of arguments passed to the method the attribute is applied to. I also have an optional LogAllResponses property that is set through a named parameter to the attribute that I use to decide how much information I will log. The public properties that receive these values are set through the constructor and attribute invocation like this:
[LogAction("Retrieve information for all ad week items with storeId: {storeId}.", LogAllResponses = false)]
The important parts of the implementation of my action filter appear below:
public class LogActionAttribute : ActionFilterAttribute
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string ParameterizedMessage { get; set; }
public bool LogAllResponses { get; set; } = true;
public LogActionAttribute(string parameterizedMessage)
{
ParameterizedMessage = parameterizedMessage;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
HttpContext.Current.Items["__Parameterized_Message__"] = ParameterizedMessage;
HttpContext.Current.Items["__Log_All_Responses__"] = LogAllResponses.ToString();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var contextualizedMessage = HttpContext.Current.Items["__Parameterized_Message__"] as string ?? "";
var logAllResponsesAsString = HttpContext.Current.Items["__Log_All_Responses__"] as string ?? "";
var logAllResponses = logAllResponsesAsString.CompareIgnoreCase("true") == 0;
// convert argument values with ID suffixes to identifiable names
var arguments = actionExecutedContext.ActionContext.ActionArguments;
//foreach (var arg in arguments)
// ...
// replace the placeholders in the parameterized message string with actual values
// log the contextualized message
//Log.Debug(...
base.OnActionExecuted(actionExecutedContext);
}
}
I've created some implementations of IValueProvider for my Web API project and I'm confused about the purpose of the ContainsPrefix method on the interface.
The ContainsPrefix method has this summary comment:
Determines whether the collection contains the specified prefix.
However the summary of the method is abstract and doesn't explain what prefix will be provided to the method or what function the method serves. Is prefix going to be the action parameter name? The action name? The controller name? The first three letters of either of these? Does this method exist to automatically figure out which action parameter the IValueProvider should provide a value for?
I never see the ContainsPrefix method get called by the Web API framework in my IValueProvider implementations although I do see references in Web API's CollectionModelBinder, MutableObjectModelBinder, SimpleModelBinderProvider, and CompositeValueProvider. For instance, the following implementation has caused me no issues in my testing:
MyValueProvider:
public class MyValueProvider : IValueProvider
{
public bool ContainsPrefix(string prefix)
{
throw new NotYetImplementedException();
}
public ValueProviderResult GetValue(string key)
{
return new ValueProviderResult("hello", "hello", CultureInfo.InvariantCulture);
}
}
TestController:
public class TestController : ApiController
{
public string Get([ModelBinder]string input)
{
return input;
}
}
GET requests to my TestController will return hello, so I know GetValue is being called from MyValueProvider, but no exception is thrown so I know that ContainsPrefix isn't called.
When can I expect ContainsPrefix to be called by the Web API framework?
What prefix will be provided to this method?
It's strange that you were able to create value provider without knowing what this (quite important) method does.
First what value providers do? In short they provide values for parameters you specify in your actions. For example:
public ActionResult Index(string test) {
}
Here we have action with parameter named "test". Where to get value for it? From value providers. There are several built-in providers, like query string or form data providers. Those providers are invoked one by one, until some of the providers will be able to provide a value. For example, if there is query string parameter "test" - query string value provider will notice that and return a value, so other providers will not be invoked. Next, if post data contains parameter "test" - it will be used, and so on.
So this parameter name ("test" in this case), is what ContainsPrefix is called with. Take for example query string value provider. If query string contains no "test" - ContainsPrefix for this provider will return "false" and next value provider will be invoked. If it returns true - GetValue should return value and no other providers will be invoked.
If you want to provide a value for parameter from say cookie, in your ContainsPrefix method you will check if there is a cookie with given name. Note that it will be called only if all default value providers will fail to provide a value.
So, TLDR: prefix represents parameter name to provide value for.
Here is parts of Pride Parrot How to create a custom session value provider article
The ContainsPrefix method is called by the model binder to determine
whether the value provider can resolve the data for a given prefix.
If you have SessionValueProvider
public class SessionValueProvider: IValueProvider
{
public bool ContainsPrefix(string prefix)
{
return HttpContext.Current.Session[prefix] != null;
}
public ValueProviderResult GetValue(string key)
{
if(HttpContext.Current.Session[key] == null)
return null;
return new ValueProviderResult(HttpContext.Current.Session[key],
HttpContext.Current.Session[key].ToString(), CultureInfo.CurrentCulture);
}
}
public class UserModel
{
public string AccountNo { get; set; }
...
}
public ViewResult SomeAction(UserModel userModel, ...)
{
...
}
At the time of model binding the DefaultModelBinder checks with the
value providers could they return value for the parameter AccountNo by
calling the ContainsPrefix method. If none of the value providers
registered eariler could return the value our SessionValueProvider
checks with session whether such a parameter is stored and if yes it
return the value.
I have a controller
[HttpGet]
[RoutePrefix("api/products/{productId}")]
public HttpResponseMessage Products(int productId,TypeEnum ptype=TypeEnum.Clothes)
{
if(!Enum.IsDefined(typeOf(TypeEnum),ptype))
//throw bad request exception
else
//continue processing
}
Myenum is declared as
public TypeEnum
{
Clothes,
Toys,
Electronics
}
Currently if,some garbage value is passed it is getting converted into default value.
What I want to do is if i call the controller as api/products/1 then the ptype should be assigned default value i.e clothes. If I call the controller as api/products/1?pType=somegarbagevalue then the controller should throw bad request exception. How can I achieve this?
Defining all your enum parameters as strings and then parsing them everywhere means you have to do this on every single action and you will need to come up with a consistent approach such that all parsing errors conform.
This is a parameter binding issue and should not be dealt with in the controller layer, it should be taken care of in the pipeline. One way to do this is to create a custom filter and add it to your config.
public class ModelStateValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
actionContext.Response = <your standardised error response>
}
}
}
And in your global.asax.cs
...
GlobalConfiguration.Configure(WebApiConfig.Register);
...
public class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
config.Filters.Add(new ModelStateValidationAttribute());
...
}
}
If you're having trouble with the model state, it's type is a ModelStateDictionary and you simply iterate over it and then it's Errors property contains all the model binding issues. e.g.
modelState = actionContext.ModelState;
modelState.ForEach(x =>
{
var state = x.Value;
if (state.Errors.Any())
{
foreach (var error in state.Errors)
{
<work your magic>
}
}
});
You have to do with string and use TryParse() to convert string to Enum value.
public HttpResponseMessage Products(int productId,string ptype="Clothes")
{
TypeEnum category = TypeEnum.Clothes;
if(!Enum.TryParse(ptype, true, out category))
//throw bad request exception if you want. but it is fine to pass-through as default Cloathes value.
else
//continue processing
}
It may look naive but the benefit of this approach is to allow ptype parameter to whatever string and to perform process without exception when ptype fails to bind the value.
This type of validation should be handled in pipeline not in controller.
public abstract class ETagMatchAttribute : ParameterBindingAttribute
{
private ETagMatch _match;
public ETagMatchAttribute(ETagMatch match)
{
_match = match;
}
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
if (parameter.ParameterType == typeof(ETag))
{
return new ETagParameterBinding(parameter, _match);
}
return parameter.BindAsError("Wrong parameter type");
}
}
something like this. refer to MSDN link for detailed explanation