Serialization error when specifying $select on ODataController endpoint - asp.net-web-api2

I've been developing a RESTful web service using Web API 2 and OWIN. Originally my controllers inherited from ApiController, and the GET actions supported OData filtering/queries i.e. marked with [EnableQuery].
We've now decided to look at whether it's feasible to expose a true OData service, and so make our controllers inherit from ODataController instead of ApiController. While this seems to be working well for the most part, $select no longer works.
public class BaseController : ODataController
{
... some properties here, not related to issue...
}
public class EmployeesController : BaseController
{
private readonly AppDbContext _context = new AppDbContext();
[EnableQuery]
public IQueryable<Employee> Get()
{
return _context.Employees;
}
...
}
The error I'm seeing is:
{
"error": {
"code": "",
"message": "An error has occurred.",
"innererror": {
"message": "'DbQuery`1' cannot be serialized using the ODataMediaTypeFormatter.",
"type": "System.Runtime.Serialization.SerializationException",
"stacktrace": " at System.Web.OData.Formatter.ODataMediaTypeFormatter.GetSerializer(Type type, Object value, ODataSerializerProvider serializerProvider)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStream(Type type, Object value, Stream writeStream, HttpContent content, HttpContentHeaders contentHeaders)\r\n at System.Web.OData.Formatter.ODataMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Web.Http.Owin.HttpMessageHandlerAdapter.<BufferResponseContentAsync>d__13.MoveNext()"
}
}
}
I'm puzzled how this works with ApiController, but not with ODataController! Is there something I'm missing?
Cheers!

Ok, I've figured out what was happening:
I tried to update my OData packages to the latest version for OData v4 support. The Microsoft .Net OData library namespaces have changed between OData v3 (System.Web.Http.OData) and v4 (System.Web.OData). I had somehow managed to mix these libraries in such a way that I was referencing the EnableQuery attribute in the old OData library, which caused the serialization issue.
Not an obvious problem to track down - it's not obvious what's happening when the attributes have the same name but are in different namespaces and in fact belong to entirely different versions!

Related

microsoft grpc-for-wcf-developers-master code fails on IIS

getting the grpc-for-wcf-developers-master, I tried to host the WCF service in the tradersys on IIS version 10 on Windows 10, which throws an exception:
Error by IIS
The AutofacServiceHost.Container static property must be set before services can be instantiated.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.InvalidOperationException: The AutofacServiceHost.Container static property must be set before services can be instantiated.
Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.
I'm aware of this issue as discussed several times here, such as this post.
Yet the code by Microsft contains the appropriate autofac container.
the question is:
Is there any special settings on IIS for resolving this issue?
as I said earlier IISExpress just works fine.
seems the AppInitialize() method in which
AutofacHostFactory.Container = builder.Build();
resides, doesn't invoke.
Based on your code, I found that you need to integrate IOC with WCF, which needs to change your code.
Here is my demo:
This is my project directory.We need to add two classes: ManualProxy and CustomServiceHostFactory.
public class CustomServiceHostFactory : ServiceHostFactory
{
protected override System.ServiceModel.ServiceHost
CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
ManualProxy.TargetFactory = () => new PortfolioService(new PortfolioRepository());
return base.CreateServiceHost(typeof(ManualProxy), baseAddresses);
}
}
This is the CustomServiceHostFactory class.
public class ManualProxy : IPortfolioService
{
private readonly IPortfolioService _target;
public static Func<object> TargetFactory;
public ManualProxy()
{
_target = (IPortfolioService)TargetFactory();
}
public Task<Portfolio> Get(Guid traderId, int portfolioId)
{
return _target.Get(traderId,portfolioId);
}
public Task<List<Portfolio>> GetAll(Guid traderId)
{
return _target.GetAll(traderId);
}
}
This is the ManualProxy class.
The SVC file needs to be changed as above picture.

Asp Core 3.1 API JsonIgnore (not) ignored in deserialization

I have this setup
Asp Core 3.1 API
Shared Lib with MyClass that is sent between API and client
Client App with Com classes
On the MyClass that is sent between them I have a field ComField that references a com class, this is only used on the client app and should not be (de)serialized, therefore I have it marked with [JsonIgnore]
class MyClass{
[JsonIgnore]
public ComThingy ComField {
get{// code here that throws the error when deserilaized on the API}
set{// code here}
}
}
When I write the API to accept the class like this, I get an error when the class is deserialized. The debugger throws the error while deserializing the MyClass, before it enters the method:
[HttpPost]
public async Task<ActionResult<MyClassReply>> Post([FromBody] MyClass myclass){
// code here
}
The API throws an exception that accessing the getter on MyClass throws an error (because that Com stuff isn't on the API).
If I deserialize manually it works fine, but then my swagger doesn't generate the whole API correctly.
[HttpPost]
public async Task<ActionResult<MyClassReply>> Post(){
// this works fine
var rdr = new StreamReader(Request.Body);
var mcj = await rdr.ReadToEndAsync();
var myclass = Newtonsoft.Json.JsonConvert.DeserializeObject<MyClass>(mcj);
// code here
}
So my question is: how come the ASP API builtin deserialization ignores the JsonIgnore attribute and still tries to deal with that property (throwing an error), and why does deserializing manually work as expected (ie ignore that property)? The default pipeline still uses NewtonSoft rght?
And how do I make the default deserialization work correctly?
Starting from ASP.NET Core 3.0, the default JSON serializer is System.Text.Json, and not Newtonsoft.Json. You need to call .AddNewtonsoftJson() in your Startup.cs to use it (see for example this answer).
Your issue might simply be that you're not using the proper JsonIgnore attribute. Both serializers have the same named attribute:
System.Text.Json.Serialization.JsonIgnoreAttribute
Newtonsoft.Json.JsonIgnoreAttribute
Maybe your using statement are importing the Newtonsoft.Json one instead of the System.Text.Json one?

Blazor HttpClient injection into a ViewModel constructor

I am following the referenced blog/article below, and I am unable to inject an HttpClient to my ViewModel. It works fine the default way (#inject in the razor file). But i am trying to inject into the ViewModel instead.
If i add to the services like the following, then the default injection doesnt work for other razor views which has the #inject HttpClient.
// manually add HttpClient to services.
services.AddTransient<IFetchViewModel, FetchViewModel>();
Question:
How can i inject the default injected HttpClient to my various
ViewModels?
Note that I am getting an exception:
WASM: Unhandled exception rendering component:
WASM: System.Reflection.TargetParameterCountException: Number of parameters specified does not match the expected number.
Reference:
https://itnext.io/a-simple-mvvm-implementation-in-client-side-blazor-8c875c365435
Update
After making suggested changes and then digging deeper during
debugging, i can see that there is something wrong with the json
deserialization. Could this be an issue?
https://github.com/aspnet/Blazor/issues/225
Note that deeper down the exception stack trace, i see the following:
WASM: at SimpleJson.SimpleJson.DeserializeObject (System.String
json, System.Type type, SimpleJson.IJsonSerializerStrategy
jsonSerializerStrategy) <0x2ebc4f0 + 0x00068> in
<8f8c03446dbf45f5bbcb1e109a064f6e>:0 WASM: at
SimpleJson.SimpleJson.DeserializeObject[T] (System.String json)
<0x2ef2490 + 0x0000a> in <8f8c03446dbf45f5bbcb1e109a064f6e>:0 WASM:
at Microsoft.JSInterop.Json.Deserialize[T] (System.String json)
<0x2ef2458 + 0x00004> in <8f8c03446dbf45f5bbcb1e109a064f6e>:0 WASM:
at
Microsoft.AspNetCore.Components.HttpClientJsonExtensions.GetJsonAsync[T]
(System.Net.Http.HttpClient httpClient, System.String requestUri)
<0x33182e0 + 0x000fa> in <13ab8f8dacb6489b93c9655168c56037>:0 WASM:
at WebUI.Features.Fetch.FetchViewModel.LoadAsync () <0x3300de0 +
0x00102> in :0
Updated 2
So i can confirm now that i was barking up the wrong tree.
Essentially, i had a deserialization issue. Once i resolved that issue, everything is working fine. Not sure if i had a DI issue from the beginning or not. Nonetheless, my issue resolved now. Thanks for all the enlightening perspectives.
This is not really an answer to your question; without complete display of your code, no answer is really possible. But let me relate to the following code snippet; perhaps the problem lies there:
// manually add HttpClient to services.
services.AddTransient<IFetchViewModel, FetchViewModel>();
HttpClient service is provided as a Singleton (CSB) by the Blazor framework. Thus you cannot inject HttpClient into a service which you add to your app as Transient. Your service should also be added as Singleton...
Hope this helps...
[Edit]
How can i inject the default injected HttpClient
to my various ViewModels?
If your ViewModels are Components, you may use the #inject directive like this:
#inject HttpClient httpClient
If your ViewModels are ordinary classes (.cs), you can
either pass a reference to an HttpClient object from your calling component methods or inject the HttpClient service to your ViewModels' constructors. Don't forget to add your Services or ViewModels in the Startup class:
services.AddSingleton<IFetchViewModel, FetchViewModel>();
Once again, use AddSingleton
No, your problem has got nothing to do with issue 225.
This issue is very old, and all the bugs referred to in this issue were rectified long before I've heard of Blazor...
Note: The exception stack trace clearly points out in the direction of HttpClient being the culprit. Do what I've suggested above, and tell us if the issue is still persisting.
Why don't you display your code, as others ask you to do. Please look for instruction on how to ask question in stack overflow.
The pattern is easy.
I got it working like this, starting from the standard starter template.
In FetchData.razor:
#page "/fetchdata"
#using ClientBlazor1.ViewModels
#inject FetchDataViewModel vm
... the html
protected override async Task OnInitAsync()
{
forecasts = await vm.GetForecasts();
}
And the ViewModel is below. You seem to be missing the constructor (-injection) part here.
Using an interface is optional, I didn't.
public class FetchDataViewModel
{
private HttpClient _httpClient;
public FetchDataViewModel(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<WeatherForecast[]> GetForecasts()
{
return await _httpClient.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
}
and to finish it up, the registration part in Startup.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<FetchDataViewModel>();
}
In general this should not be a Singleton.

ASPNETCore API ExceptionFilterAttribute and IStringLocalizer

I have a ExceptionFilterAttribute in my ASPNETCore API where I handle certain exception types.
For example any UnauthorizedAccessException I return HttpStatusCode.Unauthorized, any KeyNotFoundException I return HttpStatusCode.NotFound etc.
However, I'm also returning a basic message. For example, the string "Key Not Found" is returned with the HttpStatusCode.NotFound.
I would like the messages contained in the ExceptionFilterAttribute to be parsed using the IStringLocalizer. However, a ExceptionFilterAttribute doesn't seem to support dependency injection.
Has anyone encountered the same issue?
Is there an approach here which is considered best practice?
You can access the IServiceProvider from the ExceptionContext.
public override void OnException(ExceptionContext context)
{
var service = context.HttpContext.RequestServices.GetService<MyService>();
}

ServiceStack Authenticate attribute results in null ref exception - pull request 267

I am making an MVC3 site using ServiceStacks authentication mechanism. When I add the AuthenticateAttribute to a controller, I get a null reference exception:
System.NullReferenceException was unhandled by user code
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=ServiceStack.FluentValidation.Mvc3
StackTrace:
at ServiceStack.Mvc.ExecuteServiceStackFiltersAttribute.OnActionExecuting(ActionExecutingContext filterContext) in C:\src\ServiceStack\src\ServiceStack.FluentValidation.Mvc3\Mvc\ExecuteServiceStackFiltersAttribute.cs:line 21
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
at System.Web.Mvc.ControllerActionInvoker.<>c__DisplayClass15.<>c__DisplayClass17.<InvokeActionMethodWithFilters>b__14()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
I can see in github that pull request #267 seems to make some null ref check exactly where my code currently throws. I have another project where authentication works, but the new failing code is running on a newer version of ServiceStack and I can't see what the difference is, so any ideas about what could cause this exception for new versions of service stack (v. 3.9.18)
Here is my configuration code:
Plugins.Add(new AuthFeature(
() => new AuthUserSession(), // Here a custom IAuthSession implementation could be used instead of UserSession.
// Allow authentication by using cookies set when authenticating with username/password credentials posted to the /auth/credentials service.
new IAuthProvider[]{ new CredentialsAuthProvider() }
)
);
//Enable Funq IOC in MVC controllers.
ControllerBuilder.Current.SetControllerFactory(new FunqControllerFactory(container));
IUserAuthRepository userRepository = new OrmLiteAuthRepository(dbFactory);
container.Register(userRepository);
container.Register<ICacheClient>(new MemoryCacheClient());
// Initialise Registration feature, providing the /register route.
Plugins.Add(new RegistrationFeature());
And my Controller base class:
public abstract class ControllerBase : ServiceStackController<AuthUserSession> {
public IDbConnectionFactory Db { get; set; }
public ILog Log { get; set; }
//Common extension point for all controllers. Inherits from ServiceStack to take advantage of SS powerpack + auth.
public override string LoginRedirectUrl {
get {
return "/Auth/Login?redirect={0}";
}
}
}
I've updated the ServiceStack.Mvc NuGet package that resolved a null reference exception in (v3.9.18+). Try updating and see if that resolves it.