Return JsonNet type JSON from WebAPI in Orchard - asp.net-web-api

In standard MVC I use JsonNet to return JSON that is in camelCase and sucessfully serializes entities that have related entities (which otherwise reports a "cycles" error" using the default serializer).
I'd like to do the same for a WebAPI controller in an Orchard module. By default it returns PascalCase JSON and reports a "cyles" exception when given a list of entities.
Can anyone explain how best to configure the JSON output from within the Orchard module, to mimic what JsonNet would produce?

I've found a workaround, which is to set the JSON formatters settings to camelCase in an ActionFilter:
public class CamelCaseJsonAttribute : ActionFilterAttribute {
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
var jsonFormatter = actionContext.ControllerContext.Configuration.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
But this seems inefficient, as it gets set on each request, rather than globally, once.
I'm guessing there is an extensibility point somewhere in a module to set the HttpConfiguration - can anyone tell me one way or the other?
Many thanks.

Related

reading appSettings from asp.net core webapi

I have an asp.net core 2 webapi service. I want to read appsettings.json but I cannot figure it out. As usual, when I search the internet I get a dozen completely different answers based on various versions of the framework that ultimately don't seem to work.
As an example appsettings.json:
"AppSettings": {
"Domain": "http://localhost"
}
Firstly I tried this:
Added via nuget Microsoft.Extensions.Configuration and Microsoft.Extensions.Configuration.Json
In my ConfigureServices method I added:
services.AddSingleton<IConfiguration>(Configuration);
In my Controller I injected the config:
IConfiguration iconfig;
public ValuesController(IConfiguration iConfig)
{
iconfig = iConfig;
}
To retrieve it:
return "value: " + iconfig.GetValue<string>("AppSettings:Domain");
I can see the controller constructor being called and passing in a config, but the value is always null.
I did notice that Startup already passes in an IConfiguration. Is it a case of yet another change in implementation and i need to do something different?
[edit]
I've now read
https://joonasw.net/view/aspnet-core-2-configuration-changes
Which says it's all change again and it's auto injected, but after following the code it doesn't actually say how you get your hands on the data in your controller.
You can take a look at my answer here: https://stackoverflow.com/a/46940811/2410655
Basically you create a class that matches the properties you define in appsettings.json file (In the answer it's the AppSettings class).
And then in ConfigureServices(), you inject that class using services.Configure<>. That class will be available in all Controllers by DI.
To access its data, you can take a look at #aman's original question post from the link above.
public class HomeController : Controller
{
private readonly AppSettings _mySettings;
public HomeController(IOptions<AppSettings> appSettingsAccessor)
{
_mySettings = appSettingsAccessor.Value;
}
}

How to store PreRequestFilter information in AuthUserSession

I am building a web service using ServiceStack which has to support multiple vendors. The web service provides largely the same functionality to all vendors with some exceptions here and there.
In order to re-use as much functionality as possible I have come up with the following URL scheme:
http://localhost/brand1/templates
http://localhost/brand2/templates
"brand1" and "brand2" are not services but "templates" is. The Templates service's request DTO's will have a property called "Brand" like so:
[Route("/{Brand}/templates", "GET")]
public class GetTemplates
{
public Brand Brand { get; set; }
}
So in the Templates service I know which brand I am dealing with. This scheme works well.
What I cannot figure out though is this. The user of the service has to be authenticated and I cannot figure out how to handle the redirection of the service after the user has been authenticated since I have to pass along the brand information. I have created my own CustomAuthProvider class that inherits CredentialsAuthProvider. In the TryAuthenticate method I can set the authService.GetSession().ReferrerUrl property to the correct brand if I know what it was.
The only way I have found so far to get this information is to register a PreRequestFilter. My thinking here was that since the URL (e.g. http://localhost/brand1/templates) contains the brand I can store it in my own AuthUserSession class. I can't figure out how to do this. I have a "SessionFactory" method that I pass to the AuthFeature constructor. But what should I do in there? How do I get to the brand that I've obtained in the PreRequestFilter? Is it safe to store it in a field of the AppHost? I think not because of concurrency issues. How do I tie the PreRequestFilter to the SessionFactory method?
I hope I am explaining my problem clearly enough?
I was overthinking the solution because I didn't realize that I had all the information I needed in the IServiceBase parameter of the TryAuthenticate method of the CredentialsAuthProvider class.
In the end I came to the following solution:
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
var session = authService.GetSession();
var origQuery = authService.Request.UrlReferrer.Query;
session.ReferrerUrl = "/error";
var queryString = origQuery.Substring(10); // strip "redirect="
var decodedUrl = HttpUtility.UrlDecode(queryString);
if (!string.IsNullOrWhiteSpace(decodedUrl))
{
var query = new Uri(decodedUrl);
session.ReferrerUrl = query.AbsolutePath;
}
return DoAuthentication(userName, password);
}
}
The different places where you can set the Url to redirect to during ServiceStack Authentication, in order of precedence are:
The Session.ReferrerUrl Url if it's populated
The Continue QueryString, FormData param when making the request to /auth (i.e Authenticate.Continue property)
The HTTP Referer HTTP Header
The CallbackUrl of the current AuthProvider used

How to override a web api route?

I am trying to standardize an extension model for our REST API development team. We need to provide default implementation of routes, while allowing for custom implementations of routes that replace the default as well.
As a simple example if we have a GET route api/users like this:
public class DefaultUsersController : ApiController
{
[HttpGet]
[Route("api/users", Order = 0)]
public IEnumerable<string> DefaultGetUsers()
{
return new List<string>
{
"DefaultUser1",
"DefaultUser2"
};
}
}
We expect the default work like this:
Now a developer wants to change the behavior of that route, he should be able to simply define the same route with some mechanism to imply their implementation should be the one used, instead of the default. My initial thinking was to use the Order property on the Route attribute since that's what it appears to be there for, as a way to provide a priority (in ascending order) when an ambiguous route is discovered. However it's not working that way, consider this custom implementation that we want to override the default api/users route:
public class CustomUsersController : ApiController
{
[HttpGet]
[Route("api/users", Order = -1)]
public IEnumerable<string> CustomGetUsers()
{
return new List<string>
{
"CustomUser1",
"CustomUser2"
};
}
}
Notice the Order property is set to -1 to give it a lower priority value than the default, which is set to 0. I would have thought this would be used by the DefaultHttpControllerSelector, but it isn't. From the DefaultHttpControllerSelector:
And we end up with this exception being returned in the response:
Is it possible Microsoft just missed the logic/requirement to use Order as a route disambiguator and this is a bug? Or is there another simple way to override a route, hopefully with an attribute?
I have pretty much the same problem. I am creating a starter site, but I want users to be able to redefine to behaviour of a Controller, especially if there is a bug.
I use Autofac to resolve the Controller, but even when I register the new controller as the old one, the original one gets selected.
What I'll do is probably go with URL Rewriting. Especially since this issue is temporary in my case. However, I would be interested if someone has a better option.

ASP.NET WebAPI OData - Inheriting from EntitySetController<> but using Get(ODataQueryOptions options) rather than [Queryable]Get()

I'm using the ASP.Net WebAPI nightly build (2013-01-16) to get the latest OData support possible.
As the Meta-Me blog on MSDN OData 0.2.0-alpha release post says, there's now an EntitySetController<T> from which OData controllers can be derived to take away a lot of the pain and plumbing code.
The EntitySetController<T> class implements Get() as
[Queryable]
public virtual IQueryable<TEntity> Get()
{
throw EntitySetControllerHelpers.GetNotImplementedResponse(Request);
}
I'd like to make use of the more specific Get(ODataQueryOptions options) method offered by the ASP.Net Web API OData support.
I've coded it as
public IEnumerable<Patient> Get(ODataQueryOptions options)
{
IQueryable patients = entities.Patients;
if (options.Filter != null)
{
patients = options.Filter.ApplyTo(patients, new ODataQuerySettings());
}
return (patients as IQueryable<Patient>).AsEnumerable();
}
(I've also had this return IQueryable<> and saw someone else talk about an ODataResult - that's a type I can't discover at the moment).
However, if I try to use the ODataQueryOptions-based Get method in my own controller I get an error message about multiple actions matching the request. Specifically that error is
Multiple actions were found that match the request:
System.Collections.Generic.IEnumerable`1[Dox.Server.Model.Patient] Get(System.Web.Http.OData.Query.ODataQueryOptions) on type Dox.Server.Web.Controllers.PatientController
System.Linq.IQueryable`1[Dox.Server.Model.Patient] Get() on type System.Web.Http.OData.EntitySetController`2[[Dox.Server.Model.Patient, Dox.Server.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
I assume this is due to the route resolver (sorry if that's poor ASP.NET routing terminology) seeing Get() or Get(...) on the controller's base class as well as the controller class itself.
Questions:
a) Is there some way of adjusting routes to fix this?
b) If not, should I make my own version of EntitySetController<T> and swap out it's Get() method?
The configuration being called by Application_Start() is limited to
public static void EnableOData( HttpConfiguration config )
{
var model = BuildModelImplicitly(config);
//As per LinqPad forum: http://forum.linqpad.net/discussion/178/odata-v3-not-working
IEdmEntityContainer container = model.EntityContainers().First();
model.SetIsDefaultEntityContainer(container, true);
//config.EnableOData(model, "api");
config.Routes.MapODataRoute("OData", "api", model);
//config.EnableSystemDiagnosticsTracing();
}
There's no other configuration being called to do with routes or handlers, etc. Note that the EnableOData() method on HttpConfiguration no longer exists in the latest nightly builds as per this CodePlex discussion.
Thanks very much!
It's very cool to see that you're using our nightly builds :)
The reason you're getting a multiple matching actions error is because EntitySetController already defines a Get method. The good news is that EntitySetController also defines a QueryOptions property that you can use to retrieve the query options. So you should be able to override the EntitySetController's Get method and use the query options property instead of the parameter. It should behave exactly the same way as if you had bound the query options to an action parameter.

Creating custom RequestContext in ASP.NET MVC

I'm creating a CMS using ASP.NET MVC, and by design, I've decided that each plugin (add-on) should have a key in the incoming HTTP request. Thus, I have this general route in my host application:
{pluginKey}/{controller}/{action}/{id}
I've created a custom controller factory which implements IControllerFactory and of course, it has a method to create controllers base on the ReqeustContext and controller name. However, I want to create an artificial HttpContext (alongside all other relevant objects like HttpRequest, RequestContext, RouteData, etc.) so that controllers of plugins won't misinterpret these URL segments wrongly. In other words, I want to cut the first part of the incoming URL, and make plugins think that they're processing this URL:
{controller}/{action}/{id}
How can I achieve this?
While you could create a new implementation of all the context classes, it seems like a bit of overkill. Why not use a derived Route Handler that applies the filtering functionality before returning the HttpHandler? Here's an example:
// To avoid conflicts with similarly named controllers, I find it to be good practice
// to create a route constraint with the set of all plugin names. If you don't have
// this function already, you should be able to access it with reflection (one time
// per app lifecycle) or you hard-code them. The point is to have a regex which ensures
// only valid plugins will get selected
string[] pluginNames = GetPluginNames();
string pluginNameRegex = string.Join("|",pluginNames);
Route pluginRoute = new Route (
url: "{pluginKey}/{controller}/{action}/{id}",
defaults: null,
constraints: new RouteValueDictionary(new { pluginKey = pluginNameRegex }),
routeHandler: new PluginRouteHandler()
});
// The custom route handler can modify your route data after receiving the RequestContext
// and then send it to the appropriate location. Here's an example (markdown code/untested)
// Note: You don't have to inherit from MvcRouteHandler (you could just implement IRouteHandler
// but I'm assuming you want Mvc functionality as the fallback)
public class PluginRouteHandler : MvcRouteHandler
{
public PluginRouteHandler(IControllerFactory controllerFactory)
: base(controllerFactory)
{}
protected override IHttpHandler GetHttpHandler(RequestContext requestContext){
if(ValidatePluginRoute(requestContext))
{
// we are going to remove the pluginKey from the RequestContext, It's probably wise
// to go ahead and add it to HttpContext.Items, in case you need the data later
requestContext.HttpContext.Items["pluginKey"] = requestContext.RouteData.Values["pluginKey"];
// now let's get ride of it, so your controller factory will process the
// requestContext as you have described.
requestContext.Values.Remove("pluginKey");
// the route will now be interpreted as described so let the flow go to the MvcRouteHandler's method
}
return base.GetHttpHandler(requestContext);
}
static bool ValidatePluginRoute(RequestContext requestContext){
return requestContext.RouteData.ContainsKey("pluginKey");
}
}

Resources