Customize binding in ASP.NET Web Api - asp.net-mvc-4

I am stuck with following problem in ASP.NET Web Api. Let say I have following code in my ApiController:
public void Post(Person person)
{
// Handle the argument
}
What I would like to do is to accept following JSON request:
{
"person": {
"name": "John Doe",
"age": 27
}
}
I would like to go around creating some holding object for each model just to properly bind incoming data. In previous version of MVC, it was possible to define something like Prefix to solve this.

Let me report that I have been able to solve this implementing CustomJsonMediaTypeFormatter:
public class EmberJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
public override System.Threading.Tasks.Task<object> ReadFromStreamAsync(
Type type,
System.IO.Stream readStream,
System.Net.Http.HttpContent content,
IFormatterLogger formatterLogger)
{
return base.ReadFromStreamAsync(
typeof(JObject),
readStream,
content,
formatterLogger).ContinueWith<object>((task) =>
{
var data = task.Result as JObject;
var prefix= type.Name.ToLower();
if (data[prefix] == null)
{
return GetDefaultValueForType(type);
}
var serializer = JsonSerializer.Create(SerializerSettings);
return data[prefix].ToObject(type, serializer);
});
}
}
and replacing default JsonMediaTypeFormatter in GlobalConfiguration.

Related

How to define links for OAS3 using Swashbuckle?

I have noticed that in the Swagger UI v3 and in OAS3 we now have support for something called "links"
But I cant really figure out if its possible to use this feature with Swashbuckle, and if it is.. then how? Been searching the net and haven't found anything regarding this..
Anyone been able to use links with Swashbuckle?
You can use an OperationFilter. Create a class that implements IOperationFilter
public class MyLinkFilter : IOperationFilter
{
into it select the response for which you want to add Links.
public void Apply(OpenApiOperation operation, OperationFilterContext context)
var responses = operation.Responses;
var response = responses.FirstOrDefault(r => r.Key == "200").Value;
then update the Links property
response.Links = new Dictionary<string, OpenApiLink>
{
{
"YourKey"
,new OpenApiLink {
OperationId = "YourOperationId",
Description = ".............",
Parameters = new Dictionary<string, RuntimeExpressionAnyWrapper>
{
{
"yourParam", new RuntimeExpressionAnyWrapper
{
Any = new OpenApiString("$request.path.number")
}
}
}
}
}
};
Register your OperationFilter into startup.cs
services.AddSwaggerGen(options =>
{
options.OperationFilter<MyLinkFilter>();
});
OpenAPI, Response
OpenAPI, Link
Finally, you'll have to implement a mechanism to apply the links to the good Action in your controller.

How to change JSON returned by query using Helidon 2.0.0-M-2

I'm using Helidon 2.0.0-M2.
When I run the query below I get back a list of JSON objects.
dbClient.execute(exec -> exec.createNamedQuery("select-dsitem-by-id")
.addParam("userId", dataItemId)
.execute())
.thenAccept(response::send)
.exceptionally(throwable -> sendError(throwable, response));
Returned list
[
{
"data": "qwerty",
"user_id": "12345"
},
{
"data": "qwerty123",
"user_id": "22345"
}
]
The attribute names seem to be taken directly from the database column name. e.g. one attribute name returned is "user_id". However, I want it to be "userId". I also want to create a parent wrapper for this list like:
{
"userList": [
{
"data": "qwerty",
"user_id": "12345"
},
{
"data": "qwerty123",
"user_id": "22345"
}
]
}
What is the best way to do this with the dbclient?
Thanks
Simple approach:
Change your SQL statement to return the correct name, such as:
SELECT data, user_id as userId FROM mytable
Complicated approach:
We are working on a better support to map to a JSON stream.
Currently there is only one (a bit complicated) way to achieve this:
You can create a custom mapper from a DbRow to JsonObject. This mapper needs to be a general one (it must work for any DbRow of any query).
The built-in mapper uses metadata provided on the columns. I have prepared a simple example (that just expects to have a single type of statements):
class DbRecordMapperProvider implements DbMapperProvider {
private static final DbMapper<JsonObject> MAPPER = new DbRecordMapper();
#SuppressWarnings("unchecked")
#Override
public <T> Optional<DbMapper<T>> mapper(Class<T> aClass) {
if (JsonObject.class.equals(aClass)) {
return Optional.of((DbMapper<T>)MAPPER);
}
return Optional.empty();
}
}
class DbRecordMapper implements DbMapper<JsonObject> {
#Override
public JsonObject read(DbRow dbRow) {
return Json.createObjectBuilder()
.add("name", dbRow.column("FIRSTPART").as(String.class))
.add("message", dbRow.column("SECONDPART").as(String.class))
.build();
}
#Override
public Map<String, ?> toNamedParameters(JsonObject dbRecord) {
return dbRecord;
}
#Override
public List<?> toIndexedParameters(JsonObject dbRecord) {
throw new IllegalStateException("Cannot convert json object to indexed parameters");
}
}
The important method is public JsonObject read(DbRow dbRow).
Once you have such a DbMapperProvider, you register it with the DbClient:
dbClient = DbClient.builder()
.config(config.get("db"))
.mapperProvider(new DbRecordMapperProvider())
.build();

Swagger 2.0 with API 2.o and Odata 3.0

I been trying to implement swagger, through [Swashbuckle][1] on my application, but i get no endpoints at all on my swagger ui, and my doc just returns this
{
"swagger": "2.0",
"info": {
"version": "v1",
"title": "NB.EAM.WebAPI.V4"
},
"host": "localhost:24320",
"schemes": [
"http"
],
"paths": {},
"definitions": {}
}
In my webApiConfig i set the following configuration from following the dummys
var swagConfig = new HttpSelfHostConfiguration("http://localhost:24320");
SwaggerConfig.Register();
WebApiConfig.Register(swagConfig);
using (var server = new HttpSelfHostServer(swagConfig))
{
server.OpenAsync().Wait();
}
My swagger configuration is the standart one created by Swashbuckle:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "NB.EAM.WebAPI.Odata");
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var commentsFileName = Assembly.GetExecutingAssembly().GetName().Name + ".XML";
var commentsFile = Path.Combine(baseDirectory, "bin", commentsFileName);
c.IncludeXmlComments(commentsFile);
c.DocumentFilter<ApplyResourceDocumentation>();
c.CustomProvider(defaultProvider => new ODataSwaggerProvider(defaultProvider, c, GlobalConfiguration.Configuration).Configure(odataConfig =>
{
odataConfig.IncludeNavigationProperties();
}));
})
.EnableSwaggerUi(c =>
{
});
Any idea what i might be missing?
Edit:
here is more information about my setting
full code of my WebApiConfig:
public static void Register(HttpConfiguration config)
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor()));
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
var conventions = ODataRoutingConventions.CreateDefault();
conventions.Insert(0, new CompositeKeyRoutingConvention());
conventions.Insert(1, new CompositeKeyNavigationRoutingConvention());
conventions.Insert(2, new CountODataRoutingConvention());
ODataBatchHandler batchHandler = new UnitOfWorkBatchHandler(GlobalConfiguration.DefaultServer);
config.Routes.MapODataServiceRoute("odata", "odata", GenerateEdmModel(), new CountODataPathHandler(), conventions, batchHandler);
config.Filters.Add(new SqlExceptionFilterAttribute());
config.Filters.Add(new FilterInterceptor());
InitContentRepository();
log4net.Config.XmlConfigurator.Configure();
var swagConfig = new HttpSelfHostConfiguration("http://localhost:24320");
SwaggerConfig.Register();
WebApiConfig.Register(swagConfig);
using (var server = new HttpSelfHostServer(swagConfig))
{
server.OpenAsync().Wait();
}
}
public static Microsoft.Data.Edm.IEdmModel GenerateEdmModel()
{
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.ContainerName = "NBContext";
builder.EntitySet<as_portfolio>("as_portfolio");
builder.EntitySet<cf_usersportfolio>("cf_usersportfolio");
builder.EntitySet<as_locatportfolio>("as_locatportfolio");
builder.EntitySet<ac_bdgaset>("ac_bdgaset");
builder.EntitySet<ac_bdgcc>("ac_bdgcc");
builder.EntitySet<ac_bdgdtl>("ac_bdgdtl");
builder.EntitySet<ac_bdgloca>("ac_bdgloca");
builder.EntitySet<ac_bdgress>("ac_bdgress");
builder.EntitySet<ac_bdgsect>("ac_bdgsect");
builder.EntitySet<ac_bdgwoa>("ac_bdgwoa");
builder.EntitySet<ac_bdgwob>("ac_bdgwob");
builder.EntitySet<ac_bdgwolb>("ac_bdgwolb");
builder.EntitySet<ac_bdgwost>("ac_bdgwost");
builder.EntitySet<ac_bgdcc>("ac_bgdcc");
builder.EntitySet<ac_custome>("ac_custome");
----Very long list of enetitySets
return builder.GetEdmModel();
}
An example of my API
using NB.EAM.DataV2;
using System.Linq;
using System.Net;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.OData;
namespace NB.EAM.WebAPI.Controllers
{
public class wo_hrtypeController : BaseODataController
{
// GET: odata/wo_hrtype
[Queryable]
public IQueryable<wo_hrtype> Getwo_hrtype()
{
return this.GetWo_HrtypeBll.GetAll();
}
// GET: odata/wo_hrtype(5)
[Queryable]
public SingleResult<wo_hrtype> Getwo_hrtype([FromODataUri] string key)
{
return SingleResult.Create(this.GetWo_HrtypeBll.Find(wo_hrtype =>
wo_hrtype.lb_tyhr == key));
}
}
There is no much information to work there.
We don't know what kind of filters are being aplied on ApplyResourceDocumentation (actually, this class is on the swashbuckle.odata sample proyect and may not fit your necesities:
https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/Swashbuckle.OData.Sample/DocumentFilters/ApplyResourceDocumentation.cs).
We can't also check your entities and function definitions. Check this as an example: https://github.com/rbeauchamp/Swashbuckle.OData/blob/master/Swashbuckle.OData.Sample/App_Start/ODataConfig.cs
And we can't also check if your controllers are defined in a proper way (Methods as verbs. I think custom named methods are only taken into account if they are defined as functions)
I think I just had a similar issue. Try replacing the following line
GlobalConfiguration.Configuration.EnableSwagger(...
with this one:
swagConfig.EnableSwagger(...
The thing is that you should use here the same configuration instance that you pass to the HttpSelfHostServer constructor.
SwaggerConfig.Register(swagConfig); // pass the swagConfig instance to the auto-generated method
WebApiConfig.Register(swagConfig);
using (var server = new HttpSelfHostServer(swagConfig))
{
server.OpenAsync().Wait();
}

How to print SearchRequest

I'm trying to validate filter generation logic, so I have an instance of SearchRequest, but how to get a String representation of it ?
I don't have SearchResponse, only SearchRequest since I'm simply capturing it in my test.
This doesn't compile "Can't convert from NestSearchRequest" to byte[].
This doesn't work either, there is no Client class in Nest and ElasticClient doesn't have Serializer property (Nest 1.6.0/Elasticsearch 1.6.1).
Here's a complete example for how to get the json representation of a query, using NEST 1.6.0
void Main()
{
var client = new ElasticClient(connection: new InMemoryConnection());
var query = Query<Document>.Match(m => m
.OnField(f => f.Name)
.Query("Match This Name")
);
var json = Encoding.UTF8.GetString(client.Serializer.Serialize(query));
Console.WriteLine(json);
}
public class Document
{
public string Name { get; set; }
}
which prints the following to the console
{
"match": {
"name": {
"query": "Match This Name"
}
}
}

Custom OpenIdClient for Customer URL in MVC 4

I'm working with the default template for MVC 4 and trying to add my own openID provider for example http://steamcommunity.com/dev to the list of openID logins and an openID box where the user can type in their openID information.
To add Google I just un-comment
OAuthWebSecurity.RegisterGoogleClient();
as for other custom solutions you can do something like
OAuthWebSecurity.RegisterClient(new SteamClient(),"Steam",null);
The trouble I have is creating SteamClient (or a generic one) http://blogs.msdn.com/b/webdev/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx doesn't show anywhere to change the URL.
I think the reason I could not find the answer is that most people thought it was common sense. I prefer my sense to be uncommon.
public class OidCustomClient : OpenIdClient
{
public OidCustomClient() : base("Oid", "http://localhost:5004/") { }
}
Based on #Jeff's answer I created a class to handle Stack Exchange OpenID.
Register:
OAuthWebSecurity.RegisterClient(new StackExchangeOpenID());
Class:
public class StackExchangeOpenID : OpenIdClient
{
public StackExchangeOpenID()
: base("stackexchange", "https://openid.stackexchange.com")
{
}
protected override Dictionary<string, string> GetExtraData(IAuthenticationResponse response)
{
FetchResponse fetchResponse = response.GetExtension<FetchResponse>();
if (fetchResponse != null)
{
var extraData = new Dictionary<string, string>();
extraData.Add("email", fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email));
extraData.Add("name", fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName));
return extraData;
}
return null;
}
protected override void OnBeforeSendingAuthenticationRequest(IAuthenticationRequest request)
{
var fetchRequest = new FetchRequest();
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
request.AddExtension(fetchRequest);
}
}
Retrieving extra data:
var result = OAuthWebSecurity.VerifyAuthentication();
result.ExtraData["email"];
result.ExtraData["name"];