I'm migrating a .Net Core 2.2 web API application having API Controllers only with no views. I have a custom API response set in my project using SuperJsonOutputFormatter. Now, I'm using NewtonsoftJsonOutputFormatter for creating custom response for the APIs. But as per microsoft document services.AddMvc() is obsolete in .Net Core 3.1. So, how to call the customformatter in startup.cs. I'm using the below code
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
options.SerializerSettings.Formatting = Formatting.Indented;
});
services.AddScoped<SuperJsonOutputFormatterFilter>();
services.AddMvc(opts =>
{
opts.EnableEndpointRouting = false;
var oldFormatter = opts.OutputFormatters.OfType<CustomOutputFormatter>().Single();
opts.OutputFormatters.Remove(oldFormatter);
var replacementJsonOutputFormatter =
new CustomOutputFormatter(oldFormatter.serializerSettings, ArrayPool<char>.Shared);
opts.OutputFormatters.Add(replacementJsonOutputFormatter);
}).SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
The configure services is as below
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseRouting();
app.UseExceptionHandler("/Error");
app.UseAuthentication();
app.UseStaticFiles();
app.UseHttpsRedirection();
app.UseMvc();
}
The above code gives a runtime error that Some services are not able to be constructed, Error while validating the service descriptor. How to call customformatter without using Services.AppMvc()
My Error_Helper is as below
ErrorDescription Class
public class ErrorDescription
{
public ErrorDescription(HttpStatusCode statusCode)
{
this.Code = (int)statusCode;
this.Description = GetDescription((int)statusCode);
}
string GetDescription(int statusCode)
{
return statusCode switch
{
404 => "Employee ID not found",
500 => "Internal server error",
400 => "Device token already registered",
406 => "No data found in table",
_ => "",
};
}
[JsonProperty("errorCode")]
public int Code { get; set; }
[JsonProperty("errorDescription")]
public string Description { get; set; }
}
FormatterFilter Class
public class CustomJsonOutputFormatterFilter : IAsyncActionFilter
{
private readonly CustomOutputFormatter _formatter;
// inject SuperJsonOutputFormatter service
public CustomJsonOutputFormatterFilter(CustomOutputFormatter formatter)
{
this._formatter = formatter;
}
// a helper method that provides an ObjectResult wrapper over the raw object
private ObjectResult WrapObjectResult(ActionExecutedContext context, object obj)
{
var wrapper = new ObjectResult(obj);
wrapper.Formatters.Add(this._formatter);
context.Result = wrapper;
return wrapper;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
ActionExecutedContext resultContext = await next();
// in case we get a 500
if (resultContext.Exception != null && !resultContext.ExceptionHandled)
{
var ewrapper = this.WrapObjectResult(resultContext, new { });
ewrapper.StatusCode = (int)HttpStatusCode.InternalServerError;
resultContext.ExceptionHandled = true;
return;
}
else
{
switch (resultContext.Result)
{
case BadRequestObjectResult b: // 400 with an object
var bwrapper = this.WrapObjectResult(resultContext, b.Value);
bwrapper.StatusCode = b.StatusCode;
break;
case NotFoundObjectResult n: // 404 with an object
var nwrapper = this.WrapObjectResult(resultContext, n.Value);
nwrapper.StatusCode = n.StatusCode;
break;
case ObjectResult o: // plain object
this.WrapObjectResult(resultContext, o.Value);
break;
case JsonResult j: // plain json
this.WrapObjectResult(resultContext, j.Value);
break;
case StatusCodeResult s: // other statusCodeResult(including NotFound,NoContent,...), you might want to custom this case
var swrapper = this.WrapObjectResult(resultContext, new { result="" });
swrapper.StatusCode = s.StatusCode;
break;
}
}
}
}
Custom Outputformatter Class, this class calls the customformatterfilter
public class CustomOutputFormatter : NewtonsoftJsonOutputFormatter
{
public CustomOutputFormatter(JsonSerializerSettings serializerSettings,
ArrayPool<char> charPool) : base (serializerSettings, charPool)
{
}
public JsonSerializerSettings serializerSettings { get; private set; }
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
if (selectedEncoding == null)
if (selectedEncoding == null)
throw new ArgumentNullException(nameof(selectedEncoding));
using TextWriter writer = context.WriterFactory(context.HttpContext.Response.Body, selectedEncoding);
var statusCode = context.HttpContext.Response.StatusCode;
var rewrittenValue = new
{
status = IsSucceeded(statusCode),
error = IsSucceeded(statusCode) ? null : new ErrorDescription((HttpStatusCode)statusCode),
data = context.Object,
};
writer.Write(rewrittenValue);
this.CreateJsonWriter(writer);
await writer.FlushAsync();
}
private bool IsSucceeded(int statusCode)
{
// 204 is not an error but handled
if (statusCode >= 400 || statusCode == 204) { return false; }
return true;
}
}
I haven't looked in detail at your implementation (it seems quite convoluted, what are you trying to achieve?), but you can use UseControllers()
in the same way as you were using UseMvc()
previously to configure the MvcOptions
instance. e.g:
services.AddControllers(options =>
{
options.InputFormatters.Insert(0, new VcardInputFormatter());
options.OutputFormatters.Insert(0, new VcardOutputFormatter());
})
That might solve your problem - there's no need to call AddMvc
.
However, the error "Some services are not able to be constructed" suggests you are missing a service dependency. The error message will tell you which one. This is a new feature in .NET Core 3.1, service provider validation, that you can read about in this blog post.