I have a problem with my json formatting. I want that my json result is in camel case. I use the following code to achieve this (ASP.NET WebAPI Tip #3: camelCasing JSON):
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var formatters = config.Formatters;
var jsonFormatter = formatters.JsonFormatter;
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Newtonsoft.Json.Formatting.Indented;
settings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
}
}
The Register method is called from the Global.asax:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
// Load all modules
ESP.Framework.Web.ModuleManager.Singleton.LoadModules(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath);
// Virtual Path Provider
// // System.Web.Hosting.HostingEnvironment.RegisterVirtualPathProvider(new Provider.ESPPathProvider());
// Register Controller-Factory
//ControllerBuilder.Current.SetControllerFactory(typeof(ESP.Web.Controllers.DynamicActionControllerFactory));
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
}
But when i call the following method in my browser:
[HttpGet]
public JsonResult GetUser()
{
var user = ESP.Framework.Web.Security.UserManager.GetAllUser();
return Json(user, JsonRequestBehavior.AllowGet);
}
The result is not in camel case (The assembly in which the method is, is dynamically loaded).
Maybe someone has an solution. Thank you!
I think you are mixed mvc website and WebApi,In asp.net current version, they use different api. You set the WebApi config, but use the Mvc website's api. You should use WebApi version:
using System.Web.Http;
public class UserController : ApiController
{
[Route("user/getalluser")]
public IEnumerable<User> Get()
{
return GetAllUser();
}
}
Then you can access url 'user/getalluser' to get json data
Ok, i found a solution:
public string SerializeObject(object toSerialize)
{
var settings = new JsonSerializerSettings { ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(), Formatting = Formatting.Indented };
return JsonConvert.SerializeObject(toSerialize, Formatting.None, settings);
}
[HttpGet]
public string GetUser()
{
var user = Simplic.Framework.Web.Security.UserManager.GetAllUser();
return SerializeObject(user);
}
But it is not very nice...
Related
I'm trying to implement custom authorization and can't get the Roles to come through the attribute. If I run my code it's hitting the BasicAuth class but I can't get any roles values through. I've even tried creating a new "AllowedRoles" property to the class and that doesn't work either. What am I doing wrong? How can I pass values through the custom attribute?
public class BasicAuthAttribute : AuthorizeAttribute
{
private const string Realm = "my.api.com";
public override void OnAuthorization(HttpActionContext actionContext)
{
var r = Roles; // NULL?
//more code that's not relevant
}
public class ValuesController : ApiController
{
// GET api/<controller>
[BasicAuth(Roles = "admin")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Basic Auth Attribute
config.Filters.Add(new BasicAuthAttribute());
}
}
I had a similar problem and I found that, the first time that an action is called, the set of the properties of the authorization filter(s) for that action are called with the default value for the type of the property (e.g. null for strings).
In my case, I was "wrapping" the Roles with a custom RolesArray property that was something like:
public string[] RolesArray { get => Roles is null ? new string[0] : Roles.Split(','); set => Roles = string.Join(",", value ?? new string[0]); }
And I was always getting string[0] because there was an unexpected call to set with null as value.
The solution in my case was to prevent setting Roles if value was null, so:
set { if (value is not null) Roles = ... }
In your case, the solution is more complicated. Roles is not overridable. Furthermore, it set a private backing field that is then used in IsAuthorized Depending on how much you want to override the default implementation, you can try to shadow it with public new string Roles... and then override IsAuthorized, or you can completely re-implement the attribute by deriving from AuthorizationFilterAttribute.
I am using the following attribute [ResponseCache(Duration = 60)] to cache a specific GET Request which is called a lot on my backend in .NET Core.
Everything is working fine except the cache isn't reloaded when some data in database has changed within the 60 seconds.
Is there a specific directive I have to set to reload/update the cache? link
Example Code Snippet from my Controller:
[HttpGet]
[ResponseCache(Duration = 60)]
public ActionResult<SomeTyp[]> SendDtos()
{
var dtos = _repository.QueryAll();
return Ok(dtos);
}
There is a solution with a usage of "ETag", "If-None-Match" HTTP headers. The idea is using a code which can give us an answer to the question: "Did action response changed?".
This can be done if a controller completely owns particular data lifetime.
Create ITagProvider:
public interface ITagProvider
{
string GetETag(string tagKey);
void InvalidateETag(string tagKey);
}
Create an action filter:
public class ETagActionFilter : IActionFilter
{
private readonly ITagProvider _tagProvider;
public ETagActionFilter(ITagProvider tagProvider)
{
_tagProvider = tagProvider ?? throw new ArgumentNullException(nameof(tagProvider));
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
return;
}
var uri = GetActionName(context.ActionDescriptor);
var currentEtag = _tagProvider.GetETag(uri);
if (!string.IsNullOrEmpty(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
}
}
public void OnActionExecuting(ActionExecutingContext context)
{
var uri = GetActionName(context.ActionDescriptor);
var requestedEtag = context.HttpContext.Request.Headers["If-None-Match"];
var currentEtag = _tagProvider.GetETag(uri);
if (requestedEtag.Contains(currentEtag))
{
context.HttpContext.Response.Headers.Add("ETag", currentEtag);
context.Result = new StatusCodeResult(StatusCodes.Status304NotModified);
}
}
private string GetActionName(ActionDescriptor actionDescriptor)
{
return $"{actionDescriptor.RouteValues["controller"]}.{actionDescriptor.RouteValues["action"]}";
}
}
Initialize filter in Startup class:
public void ConfigureServices(IServiceCollection services)
{
// code above
services.AddMvc(options =>
{
options.Filters.Add(typeof(ETagActionFilter));
});
services.AddScoped<ETagActionFilter>();
services.AddSingleton<ITagProvider, TagProvider>();
// code below
}
Use InvalidateETag method somewhere in controllers (in the place where you modifing data):
[HttpPost]
public async Task<ActionResult> Post([FromBody] SomeType data)
{
// TODO: Modify data
// Invalidate tag
var tag = $"{controllerName}.{methodName}"
_tagProvider.InvalidateETag(tag);
return NoContent();
}
This solution may require a change of a client side. If you are using fetch, you can use, for example, the following library: https://github.com/export-mike/f-etag.
P.S. I didn't specify an implementation of the ITagProvider interface, you will need to write your own.
P.P.S. Articles about ETag and caching: https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
I have this code:
public ActionResult Details(string id, string detailsDate)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var tblPersonnel = new tblPersonnel();
using (var _context = new ScalehouseModel()) // for disposing
{
tblPersonnel = _context.tblPersonnels.Find(id);
}
if (tblPersonnel == null)
{
return HttpNotFound();
}
Mapper.Initialize(config => config.CreateMap<tblPersonnel, PersonnelDetailsVm>());
PersonnelDetailsVm person = Mapper.Map<tblPersonnel, PersonnelDetailsVm>(tblPersonnel);
.... // and more but the error happens on the line above.
}
After an ajax success, I am redirecting to the Details page, which the action you can see above. I am getting run time errors like so:
I have researched this and disposed my EF, but still not working and getting same error.
What do I need to do to resolve this?
In order for auto mapper to work, best way is to add automapper configuration in pipeline.
Here is how you can do
Step 1: Create a static class Mapping profile, and add all your mappings in this class
public static class MappingProfile
{
public static MapperConfiguration InitializeAutoMapper()
{
MapperConfiguration config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<tblPersonnel, PersonnelDetailsVm>();
});
return config;
}
}
Step 2: Inorder to configure, register automapper configuration in Application_Start() method in Global.asax.cs
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
//Add AutoMapper configuration here.
MappingProfile.InitializeAutoMapper();
}
Give it a try.
UPDATE
Thanks for all the answers. I am on a new project and it looks like I've finally got to the bottom of this: It looks like the following code was in fact to blame:
public static HttpResponseMessage GetHttpSuccessResponse(object response, HttpStatusCode code = HttpStatusCode.OK)
{
return new HttpResponseMessage()
{
StatusCode = code,
Content = response != null ? new JsonContent(response) : null
};
}
elsewhere...
public JsonContent(object obj)
{
var encoded = JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
_value = JObject.Parse(encoded);
Headers.ContentType = new MediaTypeHeaderValue("application/json");
}
I had overlooked the innocuous looking JsonContent assuming it was WebAPI but no.
This is used everywhere... Can I just be the first to say, wtf? Or perhaps that should be "Why are they doing this?"
original question follows
One would have thought this would be a simple config setting, but it's eluded me for too long now.
I have looked at various solutions and answers:
https://gist.github.com/rdingwall/2012642
doesn't seem to apply to latest WebAPI version...
The following doesn't seem to work - property names are still PascalCased.
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.UseDataContractJsonSerializer = true;
json.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
Mayank's answer here: CamelCase JSON WebAPI Sub-Objects (Nested objects, child objects) seemed like an unsatisfactory but workable answer until I realised these attributes would have to be added to generated code as we are using linq2sql...
Any way to do this automatically? This 'nasty' has plagued me for a long time now.
Putting it all together you get...
protected void Application_Start()
{
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;
}
This is what worked for me:
internal static class ViewHelpers
{
public static JsonSerializerSettings CamelCase
{
get
{
return new JsonSerializerSettings {
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
}
}
}
And then:
[HttpGet]
[Route("api/campaign/list")]
public IHttpActionResult ListExistingCampaigns()
{
var domainResults = _campaignService.ListExistingCampaigns();
return Json(domainResults, ViewHelpers.CamelCase);
}
The class CamelCasePropertyNamesContractResolver comes from Newtonsoft.Json.dll in Json.NET library.
It turns out that
return Json(result);
was the culprit, causing the serialization process to ignore the camelcase setting. And that
return Request.CreateResponse(HttpStatusCode.OK, result, Request.GetConfiguration());
was the droid I was looking for.
Also
json.UseDataContractJsonSerializer = true;
Was putting a spanner in the works and turned out to be NOT the droid I was looking for.
All the above answers didn't work for me with Owin Hosting and Ninject. Here's what worked for me:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
// Get the ninject kernel from our IoC.
var kernel = IoC.GetKernel();
var config = new HttpConfiguration();
// More config settings and OWIN middleware goes here.
// Configure camel case json results.
ConfigureCamelCase(config);
// Use ninject middleware.
app.UseNinjectMiddleware(() => kernel);
// Use ninject web api.
app.UseNinjectWebApi(config);
}
/// <summary>
/// Configure all JSON responses to have camel case property names.
/// </summary>
private void ConfigureCamelCase(HttpConfiguration config)
{
var jsonFormatter = config.Formatters.JsonFormatter;
// This next line is not required for it to work, but here for completeness - ignore data contracts.
jsonFormatter.UseDataContractJsonSerializer = false;
var settings = jsonFormatter.SerializerSettings;
#if DEBUG
// Pretty json for developers.
settings.Formatting = Formatting.Indented;
#else
settings.Formatting = Formatting.None;
#endif
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
The key difference is: new HttpConfiguration() rather than GlobalConfiguration.Configuration.
Code of WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//This line sets json serializer's ContractResolver to CamelCasePropertyNamesContractResolver,
// so API will return json using camel case
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
Make sure your API Action Method returns data in following way and you have installed latest version of Json.Net/Newtonsoft.Json Installed:
[HttpGet]
public HttpResponseMessage List()
{
try
{
var result = /*write code to fetch your result - type can be anything*/;
return Request.CreateResponse(HttpStatusCode.OK, result);
}
catch (Exception ex)
{
return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
}
}
In your Owin Startup add this line...
public class Startup
{
public void Configuration(IAppBuilder app)
{
var webApiConfiguration = ConfigureWebApi();
app.UseWebApi(webApiConfiguration);
}
private HttpConfiguration ConfigureWebApi()
{
var config = new HttpConfiguration();
// ADD THIS LINE HERE AND DONE
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.MapHttpAttributeRoutes();
return config;
}
}
Here's an obscure one, when the route attribute did not match the GET url but the GET url matched the method name, the jsonserializer camel case directive would be ignored e.g.
http://website/api/geo/geodata
//uppercase fail cakes
[HttpGet]
[Route("countries")]
public async Task<GeoData> GeoData()
{
return await geoService.GetGeoData();
}
//lowercase nomnomnom cakes
[HttpGet]
[Route("geodata")]
public async Task<GeoData> GeoData()
{
return await geoService.GetGeoData();
}
I have solved it following ways.
[AllowAnonymous]
[HttpGet()]
public HttpResponseMessage GetAllItems(int moduleId)
{
HttpConfiguration config = new HttpConfiguration();
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;
try
{
List<ItemInfo> itemList = GetItemsFromDatabase(moduleId);
return Request.CreateResponse(HttpStatusCode.OK, itemList, config);
}
catch (System.Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
}
}
I'm using WebApi with Breeze and I ran the same issue when trying to execute a non-breeze action into a breeze controller. I tried to use the apprach Request.GetConfiguration but the same result. So, when I access the object returned by Request.GetConfiguration I realize that the serializer used by request is the one that breeze-server use to make it's magic. Any way, I resolved my issue creating a different HttpConfiguration:
public static HttpConfiguration BreezeControllerCamelCase
{
get
{
var config = new HttpConfiguration();
var jsonSerializerSettings = config.Formatters.JsonFormatter.SerializerSettings;
jsonSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
jsonSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;
return config;
}
}
and passing it as parameter at Request.CreateResponse as follow:
return this.Request.CreateResponse(HttpStatusCode.OK, result, WebApiHelper.BreezeControllerCamelCase);
I have a basic WebApi implementation setup with the default Values controller.
After reading a blog about RESTful WebApi Versioning I decided to integrate the package into a new WebApi project.
I've added the NuGet package Microsoft.AspNet.WebApi.Versioning to assist with the versioning of my API. Here is the URL to the packages configuration instructions that I am following:
https://github.com/Microsoft/aspnet-api-versioning/wiki/Configuring-Your-Application
My values controller is very straightforward. I've added the decoration to my Get method. Here is the code:
[Authorize]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/values")]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
Unfortunately, as soon as I add the following line of code to the controller the whole thing blows up:
[Route("api/v{version:apiVersion}/values")]
Here is a peek at the error message that is being returned:
The inline constraint resolver of type 'DefaultInlineConstraintResolver' was unable to resolve the following inline constraint: 'apiVersion'.
Line 82: GlobalConfiguration.Configure(WebApiConfig.Register);
Here is the code I have inside of my Startup.cs
public void Configuration(IAppBuilder app)
{
// HTTP Configuration
HttpConfiguration config = new HttpConfiguration();
//config.MapHttpAttributeRoutes();
// Configure API Versioning
config.AddApiVersioning();
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint ) // or mvc routing?
}
};
config.MapHttpAttributeRoutes(constraintResolver);
// Configure the API to accept token authentication
ConfigureOAuthTokenConsumption(app);
// CORS
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Configure the Authorization server
ConfigureOAuth(app);
// Use WebAPI
app.UseWebApi(config);
// Moved from global.asax
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
I thought that calling AddApiVersioning and supplying the constraintResolver as outlined in the documentation would fix the issue, but it did not. Now I am struggling with what to do next.
config.AddApiVersioning();
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint ) // or mvc routing?
}
};
config.MapHttpAttributeRoutes(constraintResolver);
Any suggestions?
You should resolve the version in WebApiConfig.cs file
var constraintResolver = new DefaultInlineConstraintResolver()
{
ConstraintMap =
{
["apiVersion"] = typeof( ApiVersionRouteConstraint )
}
};
config.MapHttpAttributeRoutes(constraintResolver);
before mapping the routes. I mean before config.Routes.MapHttpRoute()
I got this error when Type as declared as string. When I changed that to int it started working.
If you pass the data in string format in route the don't use string datatype just write name of variable in {}.
Demo available in this video
https://youtu.be/Bc-z9l4HP7Y