Is there any way to log all headers using "aspnet-request:header" property with one parameter? Or should I get headers one by one like "aspnet-request:header=MyHeader" and combine them into one parameter before insert? I have lots of headers and don't want to add them seperately, I need a quick way to log them if its possible.
Currently only one at once header is supported, as it calls
string header = httpRequest.Headers[this.Header]; see source
edit: you could plug it in NLog like this:
using System;
using System.Text;
using Microsoft.AspNetCore.Http;
using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Web.Internal;
namespace NLog.Web.LayoutRenderers
{
/// <summary>
/// Render all headers for ASP.NET Core
/// </summary>
/// <example>
/// <code lang="NLog Layout Renderer">
/// ${aspnet-request-all-headers}
/// </code>
/// </example>
[LayoutRenderer("aspnet-request-all-headers")]
public class AspNetRequestAllHeadersLayoutRenderer : AspNetLayoutRendererBase
{
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpRequest = HttpContextAccessor.HttpContext.TryGetRequest();
if (httpRequest == null || httpRequest.Headers == null)
{
return;
}
foreach(var kpv in httpRequest.Headers)
{
if (header != null)
{
builder.Append(kpv.Key);
builder.Append(=);
builder.Append(kpv.Value);
}
}
}
}
}
Register it (startup.cs)
ConfigurationItemFactory.Default.LayoutRenderers
.RegisterDefinition("aspnet-request-all-headers", typeof(AspNetRequestAllHeadersLayoutRenderer ));
See also Extending NLog
usage
${aspnet-request-all-headers}
Related
I've got a couple of authorization polcies registered:
ConfigureServices()
{
services.AddAuthorization(authorisationOptions =>
{
authorisationOptions.AddPolicy(StandardAuthorizationPolicy.Name, StandardAuthorizationPolicy.Value);
authorisationOptions.AddPolicy(MutatingActionAuthorizationPolicy.Name, MutatingActionAuthorizationPolicy.Value);
});
}
& then I set a default authorization policy across all endpoints:
Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseEndpoints(endpoints =>
{
endpoints
.MapControllers()
.RequireAuthorization(StandardAuthorizationPolicy.Name); // Declaratively require the standard authorization policy on all controller endpoints
});
}
On the endpoints where I want to specify the mutating policy, I currently do the following:
[HttpPut]
[Authorize(MutatingActionAuthorizationPolicy.Name)] // Because 'PUT'. NOT DECLARATIVE! :-(
public async Task<IActionResult> AddOrUpdateOverride(SourceOverride sourceOverride, CancellationToken cancellationToken)
{
// ..
}
What I really want is a bit more control to declaritively apply the mutating policy based on the HttpVerb (i.e. POST, PUT, PATCH, DELETE).
Any idea on how to achieve that? Bonus points for allowing me to use other attributes on the controller method/class, not just [HttpPost] etc.
NB: I've seen solutions floating around that involve casting the content (and seem to revolve around a single access policy). I'd really rather stick with multiple access policies.
If I get stuck, I might end up writing a convention test for it.
You can implement a custom RequireAuthorization extension that takes HTTP verb filtering function as an argument and checks each endpoint metadata for HttpMethodAttribute
public static class AuthorizationEndpointConventionBuilderExtensions
{
/// <summary>
/// Adds authorization policies with the specified <see cref="IAuthorizeData"/> to the endpoint(s) filtered by supplied filter function
/// </summary>
/// <param name="builder">The endpoint convention builder.</param>
/// <param name="filterOnHttpMethods">Filters http methods that we applying specific policies to</param>
/// <param name="authorizeData">
/// A collection of <paramref name="authorizeData"/>. If empty, the default authorization policy will be used.
/// </param>
/// <returns>The original convention builder parameter.</returns>
public static TBuilder RequireAuthorizationForHttpMethods<TBuilder>(this TBuilder builder, Func<IEnumerable<HttpMethod>, bool> filterOnHttpMethods, params IAuthorizeData[] authorizeData)
where TBuilder : IEndpointConventionBuilder
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (authorizeData == null)
{
throw new ArgumentNullException(nameof(authorizeData));
}
if (authorizeData.Length == 0)
{
authorizeData = new IAuthorizeData[] { new AuthorizeAttribute(), };
}
builder.Add(endpointBuilder =>
{
var appliedHttpMethodAttributes = endpointBuilder.Metadata
.Where(x => x is HttpMethodAttribute)
.Cast<HttpMethodAttribute>();
if (appliedHttpMethodAttributes.Any(x => filterOnHttpMethods(x.HttpMethods
.Select(method => new HttpMethod(method)))))
{
foreach (var data in authorizeData)
{
endpointBuilder.Metadata.Add(data);
}
}
});
return builder;
}
/// <summary>
/// Adds authorization policies with the specified names to the endpoint(s) for filtered endpoints that return for filterOnHttpMethod
/// </summary>
/// <param name="builder">The endpoint convention builder.</param>
/// <param name="filterOnHttpMethods">Filters http methods that we applying specific policies to</param>
/// <param name="policyNames">A collection of policy names. If empty, the default authorization policy will be used.</param>
/// <returns>The original convention builder parameter.</returns>
public static TBuilder RequireAuthorizationForHttpMethods<TBuilder>(this TBuilder builder, Func<IEnumerable<HttpMethod>, bool> filterOnHttpMethods, params string[] policyNames)
where TBuilder : IEndpointConventionBuilder
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (policyNames == null)
{
throw new ArgumentNullException(nameof(policyNames));
}
return builder.RequireAuthorizationForHttpMethods(filterOnHttpMethods, policyNames.Select(n => new AuthorizeAttribute(n)).ToArray());
}
}
And then use this extension next to the original one:
app.UseEndpoints(endpoints =>
{
var mutatingHttpMethods = new HashSet<HttpMethod>()
{
HttpMethod.Post,
HttpMethod.Put,
HttpMethod.Delete
};
endpoints
.MapControllers()
.RequireAuthorization(StandardAuthorizationPolicy.Name)
.RequireAuthorizationForHttpMethods(httpMethods =>
httpMethods.Any(httpMethod => mutatingHttpMethods.Contains(httpMethod)),
MutatingActionAuthorizationPolicy.Name);
});
}
This is the definition added in clean1.csproj file based on NSwag's documentation
<Target Name="AfterBuild">
<Exec Command="$(NSwagExe) webapi2swagger /assembly:$(OutDir)/Clean1.dll /referencepath: $(ProjectDir) /output:$(ProjectDir)/clean1swagger.json" />
The problem is that only 200 response code is being generated like:
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/Product"
},
"x-nullable": true
}
}
Here are the XML comments at the controller's demo call.
/// <summary>
/// Gets a product by Id
/// </summary>
/// <remarks>
/// Remarks-description text.
/// </remarks>
/// <response code="200">test 200</response>
/// <response code="201">test 201/response>
/// <response code="400">test 400</response></response>
[HttpGet]
[ResponseType(typeof(Product))]
public IHttpActionResult GetProduct(int id)
{
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
The json should include and generate automatically the other responses.
This is currently not possible. We considered adding this feature but in most cases you need to specify the type and this cannot be done via xml comments. For now you have to use the SwaggerResponseAttribute for that. But please create an issue on Github so that the feature is considered to be added in the future...
I am developing an ASP.NET application that uses ODataApiController. The application shows users a grid by querying data and showing it in a table. I would like the ability to export to a number of different formats, including CSV and a custom XML format. Ideally, I would just take the same OData query the grid uses, set the Accepts header, and get back CSV or XML.
I've created MediaTypeFormatters to do what I need, but they only work with "regular" ApiController, not ODataApiController. Looking at the code in github, I see that OData has it's own MediaTypeFormatter scheme to handle various cases, and built in XML and JSON formatters. But I can't see how to hook into this to provide custom formats. I've attempted inheriting ODataMediaTypeFormatter, but a breakpoint set on it never hits.
I am only really interested in output formats here. How can I extend OdataApi to output different formats?
You can use MediaTypeFormatter on OData queries as well. Just add a new class to your project that inherit MediaTypeFormatter. Then add this to your WebApiConfig file on Register:
config.Formatters.Add(new JSONPFormatter(new QueryStringMapping("$format","jsonp","application/javascript")));
If you then query your entity with the $format=jsonp it will return the entity as JSONP. You can also request it with the contenttype application/javascript to get a JSONP return.
Here is a full example for a MediaFormatter for JSONP return. You could easily change it for your need:
using MyProject.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.ServiceModel.Syndication;
using System.Threading.Tasks;
using System.Web;
using System.Xml;
using Newtonsoft.Json;
namespace SMSIdent.Modules.Formatter
{
/// <summary>
/// Adds a $format=jsop to all odata query
/// </summary>
public class JSONPFormatter : MediaTypeFormatter
{
private readonly string jsMIME = "application/javascript";
public JSONPFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue(jsMIME));
}
public JSONPFormatter(MediaTypeMapping mediaTypeMapping) : this()
{
MediaTypeMappings.Add(mediaTypeMapping);
}
//THis checks if you can POST or PUT to this media-formater
public override bool CanReadType(Type type)
{
return false;
}
//this checks if you can GET this media. You can exclude or include your Resources by checking for their types
public override bool CanWriteType(Type type)
{
return true;
}
//This actually takes the data and writes it to the response
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
//you can cast your entity
//MyType entity=(MyType) value;
var callback=HttpContext.Current.Request.Params["callback"];
return Task.Factory.StartNew(() =>
{
using (StreamWriter sw = new StreamWriter(writeStream))
{
if (string.IsNullOrEmpty(callback))
{
callback = "values";
}
sw.Write(callback + "(" + JsonConvert.SerializeObject(value, Newtonsoft.Json.Formatting.None,
new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}) + ")");
}
});
}
}
}
Note: I'am using Web API 2. I don't know exactly if it also works in Web Api 1.
If i have [Required(AllowEmptyStrings = true)] declaration in my view model the validation is always triggered on empty inputs. I found the article which explains why it happens. Do you know if there is a fix available? If not, how do you handle it?
Note: I'm assuming you have AllowEmptyStrings = true because you're also using your view model outside of a web scenario; otherwise it doesn't seem like there's much of a point to having a Required attribute that allows empty strings in a web scenario.
There are three steps to handle this:
Create a custom attribute adapter which adds that validation parameter
Register your adapter as an adapter factory
Override the jQuery Validation function to allow empty strings when that attribute is present
Step 1: The custom attribute adapter
I modified the RequiredAttributeAdapter to add in that logic:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace CustomAttributes
{
/// <summary>Provides an adapter for the <see cref="T:System.Runtime.CompilerServices.RequiredAttributeAttribute" /> attribute.</summary>
public class RequiredAttributeAdapter : DataAnnotationsModelValidator<RequiredAttribute>
{
/// <summary>Initializes a new instance of the <see cref="T:System.Runtime.CompilerServices.RequiredAttributeAttribute" /> class.</summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <param name="attribute">The required attribute.</param>
public RequiredAttributeAdapter(ModelMetadata metadata, ControllerContext context, RequiredAttribute attribute)
: base(metadata, context, attribute)
{
}
/// <summary>Gets a list of required-value client validation rules.</summary>
/// <returns>A list of required-value client validation rules.</returns>
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRequiredRule(base.ErrorMessage);
if (base.Attribute.AllowEmptyStrings)
{
//setting "true" rather than bool true which is serialized as "True"
rule.ValidationParameters["allowempty"] = "true";
}
return new ModelClientValidationRequiredRule[] { rule };
}
}
}
Step 2. Register this in your global.asax / Application_Start()
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(typeof(RequiredAttribute),
(metadata, controllerContext, attribute) => new CustomAttributes.RequiredAttributeAdapter(metadata,
controllerContext, (RequiredAttribute)attribute));
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}
Step 3. Override the jQuery "required" validation function
This is done using the jQuery.validator.addMethod() call, adding our custom logic and then calling the original function - you can read more about this approach here. If you are using this throughout your site, perhaps in a script file referenced from your _Layout.cshtml. Here's a sample script block you can drop in a page to test:
<script>
jQuery.validator.methods.oldRequired = jQuery.validator.methods.required;
jQuery.validator.addMethod("required", function (value, element, param) {
if ($(element).attr('data-val-required-allowempty') == 'true') {
return true;
}
return jQuery.validator.methods.oldRequired.call(this, value, element, param);
},
jQuery.validator.messages.required // use default message
);
</script>
Rather than decorating the value with the 'Required' attribute, I use the following. I find it to be the simplest solution to this issue.
[DisplayFormat(ConvertEmptyStringToNull=false)]
Embarrassing question really -- I have Subsonic collection, then I filter out some data using Where.
MyColl.Where(it => it.foo()==true)
now I would like to pass those data still as Subsonic collection. How to do it (the most properly way)? I checked constructor for Subsonic collection, ToX() methods, and googled.
edit: Subsonic 2.x series.
Thank you in advance, and sorry for naive question.
In SubSonic 2.x, the data isn't actually filtered out with Where() until the query is re-executed against the database. I'm assuming that's the same in 3.0. In 2.x there was a .Filter() method that would do what you are looking for.
The .Filter() method gets added to the generated classes. Here's what mine looks like (it's customized from the default):
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// All existing wheres are retained.
/// </summary>
/// <returns>TblSomethingOrOtherCollection</returns>
public TblSomethingOrOtherCollection Filter(SubSonic.Where w)
{
return Filter(w, false);
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Existing wheres can be cleared if not needed.
/// </summary>
/// <returns>TblSomethingOrOtherCollection</returns>
public TblSomethingOrOtherCollection Filter(SubSonic.Where w, bool clearWheres)
{
if (clearWheres)
{
this.wheres.Clear();
}
this.wheres.Add(w);
return Filter();
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Thanks to developingchris for this!
/// </summary>
/// <returns>TblSomethingOrOtherCollection</returns>
public TblSomethingOrOtherCollection Filter()
{
for (int i = this.Count - 1; i > -1; i--)
{
TblSomethingOrOther o = this[i];
foreach (SubSonic.Where w in this.wheres)
{
bool remove = false;
System.Reflection.PropertyInfo pi = o.GetType().GetProperty(w.ColumnName);
if (pi != null && pi.CanRead)
{
object val = pi.GetValue(o, null);
if (w.ParameterValue is Array)
{
Array paramValues = (Array)w.ParameterValue;
foreach (object arrayVal in paramValues)
{
remove = !Utility.IsMatch(w.Comparison, val, arrayVal);
if (remove)
break;
}
}
else
{
remove = !Utility.IsMatch(w.Comparison, val, w.ParameterValue);
}
}
if (remove)
{
this.Remove(o);
break;
}
}
}
return this;
}
}
For some reason i could never get the inline method of filter to work, but its easy to use like this:
SubSonic.Where w = new Where();
w.ColumnName = Product.CatIDColumn.PropertyName;
w.Comparison = Comparison.Equals;
w.ParameterValue = "1";
ProductCollection objFilteredCol = objProdCollection.Where(w).Filter();