How to provide custom mediatype formats for OData Api - asp.net-web-api

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.

Related

Visual studio how to add custom tool/code generator configuration options

I wanted to easily turn some json schema files into classes. So googling I found NJsonSchema and I implemented this in a visual studio custom tool so I can set this on relevant json files and get my classes out. This al works and I pasted the code below. This code comes from this very answer. Though it does need a little updating for VS2022.
I find that documentation on how to do this is rather rare and the thing I am missing is how I can add something like configuration options for the custom tool.
Take for example the line "ClassStyle = CSharpClassStyle.Record," that is something one might want configurable for the user. But I cannot find anything on how to do that. Anyone have a good pointer/answer on this?
Preferably a way to add take the config from some custom properties in the file its properties that are available when the custom tool is configured on a project file.
using System;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System.Text;
using NJsonSchema.CodeGeneration.CSharp;
using NJsonSchema;
namespace ClassGeneratorForJsonSchema
{
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("GenerateClassesFromJsonSchema", "Use NJsonSchema to generate code from a json schema.", "1.0")]
[Guid("202E7E8B-557E-46CB-8A1D-3024AD68F44A")]
[ComVisible(true)]
[ProvideObject(typeof(ClassGeneratorForJsonSchema))]
[CodeGeneratorRegistration(typeof(ClassGeneratorForJsonSchema), "ClassGeneratorForJsonSchema", "{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}", GeneratesDesignTimeSource = true)]
public sealed class ClassGeneratorForJsonSchema : IVsSingleFileGenerator
{
#region IVsSingleFileGenerator Members
public int DefaultExtension(out string pbstrDefaultExtension)
{
pbstrDefaultExtension = ".cs";
return pbstrDefaultExtension.Length;
}
public int Generate(string wszInputFilePath, string bstrInputFileContents,
string wszDefaultNamespace, IntPtr[] rgbOutputFileContents,
out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
{
try
{
var schema = JsonSchema.FromJsonAsync(bstrInputFileContents).Result;
var generator = new CSharpGenerator(schema, new CSharpGeneratorSettings()
{
JsonLibrary = CSharpJsonLibrary.SystemTextJson,
ClassStyle = CSharpClassStyle.Record,
Namespace = wszDefaultNamespace
});
byte[] bytes = Encoding.UTF8.GetBytes(generator.GenerateFile());
int length = bytes.Length;
rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(length);
Marshal.Copy(bytes, 0, rgbOutputFileContents[0], length);
pcbOutput = (uint)length;
}
catch (Exception ex)
{
pcbOutput = 0;
}
return VSConstants.S_OK;
}
#endregion
}
}

Net Core NLog.Web "aspnet-request:header" property usage?

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}

To disable Get Method if Sensitive Information is passed

Our Application (MVC Based) accepts user payment information update request over GET method.Default method used by the application is POST.
Currently if we pass any sensitive information over a GET Method via Querystring, then Request sucessfully works.The reason is that it hits the same Edit Action method in Controller
[HttpGet]
[ValidateRequest(true)]
public ActionResult Edit (parameters)
But what we want is that Any request with sensitive information (like Credit Card etc.) sent over a GET method should be rejected by the application.
Anyhow can we reject GET method through Routing if sensitive information is passed? Please suggest valid approach.
My current route that calls Action is mentioned below:
routes.MapRoute("ChargeInformation", "ChargeInformationt.aspx/{seq}", new { controller = "Payment", action = "Edit", seq = UrlParameter.Optional });
Routing's only responsibility is to map URLs to route values and from route values back to URLs. It is a separate concern than authorizing the request. In fact, the built-in routing extension methods (MapRoute, MapPageRoute, and IgnoreRoute) completely ignore the incoming query string.
For request authorization, MVC has an IAuthorizationFilter interface that you can hook into. You can also (optionally) combine it with an attribute to make it run conditionally on specific action methods, as shown below.
In this case, you just want to reject specific query string key names that are passed into the request. It is unclear what action you wish to take in this case, so I am just setting to HTTP 403 forbidden as an example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Mvc;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DisallowQueryStringKeysAttribute : FilterAttribute, IAuthorizationFilter
{
private readonly IEnumerable<string> keysSplit;
public DisallowQueryStringKeysAttribute(string keys)
{
this.keysSplit = SplitString(keys);
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var queryStringKeys = filterContext.HttpContext.Request.QueryString.AllKeys;
// If any of the current query string keys overlap with the non-authorized keys
if (queryStringKeys.Intersect(this.keysSplit, StringComparer.OrdinalIgnoreCase).Any())
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
// You must set the result property to a handler to run to tell the
// framework that the filter should do something other than run the
// action method. In this case, we just set it to an empty result,
// which implements the null object pattern. You could (if so inclined),
// make a class to set the status code or do something else
// (such as redirect) to indicate that the request is invalid.
filterContext.Result = new EmptyResult();
}
}
private string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
Usage
[HttpGet]
[ValidateRequest(true)]
[DisallowQueryStringKeys("creditCard, password")]
public ActionResult Edit (string creditCard, string password)

HL7 FHIR serialisation to json in asp.net web api

I'm using the HL7.Fhir nuget package 0.9.3 created by Ewout Kramer.
I am tying it up with ASP.NET Web API, but unfortunately the built in JSON serialization isn't generating the JSON correctly. It contains lots of this:
"<IdentifierElement>k__BackingField"
As suggested by the framework, this code works...
public HttpResponseMessage GetConformance()
{
var conformance = new Conformance();
var json = FhirSerializer.SerializeResourceToJson(conformance);
return new HttpResponseMessage{Content = new StringContent(json)};
}
but this will become quite repetitive and isn't following the "by convention" json/xml serialization methods of Web API.
Are there any other FHIR objects packages available or should I just write my own?
Although the newer version of the HL7.Fhir NuGet package (currently in beta) will carry additional [DataContract] and [DataMember] attributes, and thus prevent these kind of errors, the standard .NET DataContract serializer will not be able to serialize in-memory POCO's to the correct FHIR XML and Json representation. The FHIR serialization has specific rules about how both XML and json are used, which is hard, if not impossible, to configure using the (limited) possibilities of the DataContract serializer.
However, it's also not necessary to invoke the FhirSerializer for each call as you showed in your codesnippet (in fact, that would be an WebApi anti-pattern). For example, our FHIR server (at http://spark.furore.com/fhir) is based on WebApi and uses a custom MediaTypeFormatter to handle this. To get a taste of what that looks like, we have created two formatters, one for json and one for xml:
public class JsonFhirFormatter : MediaTypeFormatter
{
public JsonFhirFormatter() : base()
{
foreach (var mediaType in ContentType.JSON_CONTENT_HEADERS)
SupportedMediaTypes.Add(new MediaTypeHeaderValue(mediaType));
}
}
This tells the framework this formatter will take any of the formats in ContentType.JSON_CONTENT_HEADERS (which are application/json and some common variants) and is able to parse and read FHIR ModelTypes:
public override bool CanReadType(Type type)
{
return type == typeof(ResourceEntry) || type == typeof(Bundle) || (type == typeof(TagList));
}
public override bool CanWriteType(Type type)
{
return type == typeof(ResourceEntry) || type == typeof(Bundle) || (type == typeof(TagList)) || type == typeof(OperationOutcome);
}
Finally, you have to override the ReadFromStreamAsync and WriteToStreamAsync methods:
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
// Some code left out...
XmlWriter writer = new XmlTextWriter(writeStream, Encoding.UTF8);
if (type == typeof(ResourceEntry))
{
ResourceEntry entry = (ResourceEntry)value;
FhirSerializer.SerializeResource(entry.Resource, writer);
content.Headers.SetFhirTags(entry.Tags);
}
Now, once you've done that, your Controller can simply do:
[HttpGet, Route("metadata")]
public ResourceEntry Metadata()
{
return service.Conformance();
}
[HttpOptions, Route("")]
public ResourceEntry Options()
{
return service.Conformance();
}
Note that our server does not use Resources as parameters and return values in the controller. Resources won't allow you to capture important metadata (like the id, version-id, last modified date etc). By using ResourceEntry in my controller, this data can be passed around with the resource data and the WebApi framework can bind this metadata to the appropriate HTTP headers.

Telerik RadGrid and RadComboBox filtering with a separate data class?

I'm trying to implement a GridBoundColumn for filtering as described in this Telerik demo.
The example queries the database directly using SqlDataAdapter, but I want to use an existing class elsewhere in my project, and configure the DataSource of the filter RadComboBox in the RadGrid to use the LINQ data context common to the rest of my project.
namespace MyProject.DataLib
{
// Data context lives here.
}
namespace MyProject.UI
{
public partial class MyUI : PageBase
{
public class rgcFilterColumn : GridBoundColumn
{
...
protected void list_ItemsRequested(object o, RadComboBoxItemsRequestedEventArgs e)
{
using (MyProject.DataLib = new DataLib(CurrentUser)) // error CurrentUser
{
((RadComboBox)o).DataTextField = DataField;
((RadComboBox)o).DataValueField = DataField;
((RadComboBox)o).DataSource = ???; // LINQ would go here...?
((RadComboBox)o).DataBind();
}
}
}
}
}
The user defined by CurrentUser has the necessary credentials, however when I try to do this (which I know is wrong):
Cannot access non-static property 'CurrentUser' in static context.
What would be the best way to accomplish what I want here, as well as clarify my incomplete understanding of why I can't simply talk to my existing data context?
Found the solution, should've just looked around more closely.

Resources