Does OData actually work in AspNetCore on Linux? - asp.net-core

I work in an environment where all new work is done in AspNetCore, one of the primary reasons being so we can run it on Linux servers. We have an API to access one of our databases that I've been asked to add OData to. No problem.
The Problem
I've got a lovely example working in a test project and I'm moving it over to the real API in a branch of the code annnnnnd.....what's that? It's a reference to Microsoft.AspNet.
My test project is .NetCore 2.1, and the only NuGet packages installed are:
Microsoft.AspNetCore.App v2.1.1
Microsoft.AspNetCore.OData v7.0.1 (tried v7.1.0 too)
Microsoft.AspNetCore.Razor.Design v2.1.2
Microsoft.NETCore.App v2.1.0
This (truncated) code works great on my Windows development machine, but I foresee problems when we try to build it for Linux deployment.
Startup.cs - Notice the first 2 usings
using Microsoft.AspNet.OData.Builder;
using Microsoft.AspNet.OData.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OData.Edm;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using ODataTest.Models;
namespace ODataTest
{
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddOData();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseMvc(b =>
{
b.Filter().Expand();
b.MapODataServiceRoute("odata", "odata", GetEdmModel());
b.EnableDependencyInjection();
});
}
private static IEdmModel GetEdmModel()
{
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<ThingDto>(nameof(ThingDto));
return builder.GetEdmModel();
}
}
}
ThingController.cs - Notice using #3
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.OData;
using Microsoft.AspNetCore.Mvc;
using ODataTest.Models;
namespace ODataTest.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ODataController
{
private readonly Db _db;
public ValuesController(Db db)
{
this._db = db;
}
[HttpGet]
[EnableQuery]
public ActionResult<IEnumerable<ProductPricePointMarkdownDto>> Index()
{
var things =
from thing in _db.Things
select new ThingDto
{
ThingID = thing.ID,
StyleID = thing.StyleID,
ColourID = thing.ColourID
};
return Ok(things);
}
}
}
ThingDto.cs - Notice the last using
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.OData.Query;
namespace ODataTest.Models
{
[Filter("ColourID", Disabled = true)]
[Filter]
public class ThingDto
{
[Key]
public int ThingID { get; set; }
public int StyleID { get; set; }
public int ColourID { get; set; }
}
}
Can anyone steer me away from my current thinking that OData "works with Core" is marketing, and in reality it doesn't?

So the answer is "Yes, it does work". I have not tracked down whether it's a bad namespace, or actually referring to .NET Standard. The motivation to find out went once I proved this ran on a Linux docker container.

Related

Why IHttpContextAccessor always get null IP in Blazor server-side?

The SDK I am using is 5.0.100-rc.2.20479.15.
The target framework also is .Net 5.
Here is my code in startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace Sample
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHttpContextAccessor();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
}
And here is my code in index.razor:
#page "/"
#inject IHttpContextAccessor httpContextAccessor
<h1>#IP</h1>
#code{
public string IP{get;set;}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
IP=httpContextAccessor.HttpContext.Connection?.RemoteIpAddress.ToString();
}
}
}
I published it to the server computer and the IP always returns null.
My program needs to submit a form. And the form is only allowed to submit once per day by the same people(without an account).
So I need to get the IP and set an IMemoryCache with a one-day DateTimeOffset.
In spite, storing the IP address is not a perfect way to solve my feature but it seems I can only do it like this.
The server-side of Blazor Server App communicates with the front-end (Browser) via SignalR, not HTTP. Thus the HttpContext object is not available in Blazor Server App, except on the initial request to the app, which is always an HTTP request. This is the only opportunity when you can access the HttpContext. Note that the _Host.cshtml file is a Razor Page file, so you can put some code in it that access the HttpContext directly, and get whatever data you want to read from the HttpContext, such as the Remote IP, etc. You can then pass the data retrieved to your Blazor SPA as parameters of the component tag helper present in the _Host.cshtml. Add code in the OnInitialized life-cycle method of the App component to get the data, as for instance define a property that gets the remote IP address of the current user. You can then store this data in the local storage for a later use
WARNING: AS #enet comment suggests this is not the recommended way to go about this in Blazor
In case it helps anyone... Here is my interpretation of #Melon NG's suggestion
In _Host.cshtml.cs
using AutoCheck.BlazorApp.Components;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AutoCheck.BlazorApp.Pages
{
public class Host : PageModel
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IRememberMe _rememberMe;
public Host(IHttpContextAccessor httpContextAccessor, IRememberMe rememberMe)
{
_rememberMe = rememberMe;
_httpContextAccessor = httpContextAccessor;
}
public void OnGet()
{
_rememberMe.RemoteIpAddress = _httpContextAccessor.HttpContext.Connection.RemoteIpAddress;
}
}
}
RememberMe
using System.Net;
namespace AutoCheck.BlazorApp.Components
{
public interface IRememberMe
{
public IPAddress RemoteIpAddress { get; set; }
}
public class RememberMe : IRememberMe
{
public IPAddress RemoteIpAddress { get; set; }
}
}
About.razor
#page "/About"
#inject IRememberMe RememberMe
<h3>About</h3>
<table class="table table-striped">
<thead>
<td>Item</td>
<td>Value</td>
</thead>
<tr>
<td>Remote IP</td>
<td>#RememberMe.RemoteIpAddress</td>
</tr>
</table>
In ConfigureServices in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
//services.AddSingleton<IRememberMe, RememberMe>();
services.AddScoped<IRememberMe, RememberMe>();
...
}

.NET CORE web api Changes not detected

I'm currently working on a VueJS app using .NET Core 2.2 for the back-end part.
I was working on it for a few months but it suddenly stopped working like a charm when I updated from 2.0 to 2.2.
My web API changes are not detected and I don't know why.
For instance, I have a few controllers and whenever I change them, and then use the web API, the changes are not made. I can even delete the whole file and the web API using this file will still be working!
Another problem I get is that when I create new controller files, it's not detected; I'm stuck with my old controllers, which I'm not able to update.
Others files updates are detected (at least if I change the VueJS front-end)
I can also change the providers, delete whatever file used for the web API, changes are not detected. It may be a configuration issue?
Is there anything I could try to make things update again?
Update: I can change whatever I want in the back-end and it will do nothing. Compilations errors are the only problem I have to care about, it's like the app doesn't use the code anymore.
Here is an example I can provide:
I have a controller InterventionController which retrieve data about operations (I am french in a french context so variables names, etc will be in french) :
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Vue2Spa.Models;
using Vue2Spa.Providers;
namespace Vue2Spa.Controllers
{
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class InterventionController : Controller
{
private readonly IInterventionProvider interventionProvider;
public InterventionController(IInterventionProvider interventionProvider)
{
this.interventionProvider = interventionProvider;
}
[HttpGet("[action]")]
public IActionResult Interventions([FromQuery(Name = "from")] int from = 0, [FromQuery(Name = "to")] int to = 5000)
{
var quantity = to - from;
if (quantity <= 0)
{
return BadRequest("La quantité doit être positive !");
}
else if (from < 0)
{
return BadRequest("Vous devez spécifier un indice de départ non nul !");
}
var allInterventions = interventionProvider.GetInterventions();
var result = new
{
TotalInterventions = allInterventions.Count,
Interventions = allInterventions.Skip(from).Take(quantity).ToArray()
};
return Ok(result);
}
}
// Others methods not useful for my example
}
It calls a provider which has the following code:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Vue2Spa.Models;
namespace Vue2Spa.Providers
{
public class DBInterventionProvider : IInterventionProvider
{
private List<Intervention> interventions { get; set; }
DbContextOptionsBuilder<DepouillementTestContext> optionsBuilder = new DbContextOptionsBuilder<DepouillementTestContext>();
public DBInterventionProvider()
{
optionsBuilder.UseSqlServer(credentials); // Credentials are correct but not including it there for obvious reasons
using (var context = new LECESDepouillementTestContext(optionsBuilder.Options))
{
interventions = context.Intervention.ToList();
}
}
public List<Intervention> GetInterventions()
{
using (var context = new LECESDepouillementTestContext(optionsBuilder.Options))
{
interventions = context.Intervention.ToList();
}
return interventions;
}
// Others methods not useful for this example
}
}
I can delete these files, and I'm still able to access my operations web API
If needed, here is my startup.cs file:
using System;
using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Vue2Spa.Models;
namespace Vue2Spa
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Additional code for SQL connection
services.AddDbContext<DepouillementTestContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"],
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.
MigrationsAssembly(
typeof(Startup).
GetTypeInfo().
Assembly.
GetName().Name);
//Configuring Connection Resiliency:
sqlOptions.
EnableRetryOnFailure(maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
// Changing default behavior when client evaluation occurs to throw.
// Default in EFCore would be to log warning when client evaluation is done.
options.ConfigureWarnings(warnings => warnings.Throw(
RelationalEventId.QueryClientEvaluationWarning));
});
// Provider pour les interventions
services.AddSingleton<Providers.IInterventionProvider, Providers.DBInterventionProvider>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Webpack initialization with hot-reload.
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true,
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
}
Thanks in advance,
Well I've found why I had this problem, and I feel kinda dumb for that but well, it's working now.
I didn't change my launch.json when I upgraded from .NETCORE 2.0 to 2.2, all I had to do was changing
"program": "${workspaceFolder}/content/bin/Debug/netcoreapp2.0/Vue2Spa.dll",
by
"program": "${workspaceFolder}/content/bin/Debug/netcoreapp2.2/Vue2Spa.dll",
For more informations, see : https://learn.microsoft.com/en-us/aspnet/core/migration/21-to-22?view=aspnetcore-2.2&tabs=visual-studio

ASP.NET Core Web API Error: Model 1[TContext] violates the Constraint of type 'TContext'

I have a Solution in Visual Studio 2017 that contains the following Projects:
CredentialManager.API (ASP.NET Core 2.1 Web API project)
CredentialManager.Models (Class Library that contains the Domain Model and Data Context Class)
The Domain Model Class is coded as follows:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace CredentialManager.Models.Entities
{
public class Credential
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long CredentialId { get; set; }
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string Application { get; set; }
}
}
The Data Context Class is as follows:
using System;
using System.Collections.Generic;
using System.Text;
using CredentialManager.Models.Entities;
using Microsoft.EntityFrameworkCore;
namespace CredentialManager.Models.Context
{
public class CredentialManagerContext : DbContext
{
public CredentialManagerContext(DbContextOptions options)
: base(options)
{ }
public DbSet<Credential> Credentials { get; set; }
}
}
The appsettings.json file looks like the following:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
"i.": null,
"CredentialManagerDB": "server=.\\SQLEXPRESS;database=CredentialManagerDB;Trusted_Connection=true;"
},
"AllowedHosts": "*"
}
The Startup.CS file looks like this:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddDbContext<CredentialManagerContext>(o => o.UseSqlServer(Configuration["ConnectionStrings:CredentialManagerDB"]));
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
I then build the Solution and Added Migrations. But when I run update-database, I get the following error:
GenericArguments[0], 'CredentialManager.Models.Migrations.CredentialManagerContext', on 'Microsoft.EntityFrameworkCore.Design.IDesignTimeDbContextFactory`1[TContext]' violates the constraint of type 'TContext'.
Can someone here throw some light on this error ? If I include the classes and data context in the same folder as the API project, then everything works.. But I want these classes to be part of a separate Class Library Project. Any help would be much appreciated.
Thanks.
Update context file to have the following:
public CredentialManagerContext(DbContextOptions<CredentialManagerContext> options)
: base(options)
{ }
As outlined in the documentation:
This requires adding a constructor argument to your DbContext type that accepts :
DbContextOptions<TContext>
This should resolve your issue.
Thank you for all the suggestions. I found a Solution as well. The Startup.cs needs to be informed about the Project that contains the Migrations:
services.AddDbContext<CredManagerContext>(options => options.UseSqlServer(Configuration.GetConnectionString("CredentialManagerDB"), x => x.MigrationsAssembly("CredManager.Models")));
Everything works perfectly after this.

structuremap configuration asp.net mvc 4

I have a problem with MVC4 StructureMap configuration, when I run the project the compiler fires this error
No Default Instance defined for PluginFamily Mace_CrmSystem.Controllers.HomeController
this is my code
global.aspx code
namespace Mace_CrmSystem
{
// Note: For instructions on enabling IIS6 or IIS7 classic mode,
// visit http://go.microsoft.com/?LinkId=9394801
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteTable.Routes.MapRoute("Oqla", "Oqla", new { controller = "Home", action = "index" });
RouteConfig.RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new MyCustomeFactory());
ObjectFactory.Initialize(x => x.For<string>().Use<string>());
}
}
}
MycustomeFactory class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using StructureMap;
namespace Mace_CrmSystem
{
public class MyCustomeFactory : System.Web.Mvc.DefaultControllerFactory
{
protected override System.Web.Mvc.IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
return StructureMap.ObjectFactory.GetInstance(controllerType) as System.Web.Mvc.IController;
}
}
}
Controller class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Mace_CrmSystem.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public HomeController(string parameter)
{
TempData["Hi"] = "Hi";
}
public ActionResult Index()
{
return View();
}
}
public class logger
{
public void log()
{
}
}
}
what I noticed that when I add a parameter of type object like
public HomeController(logger parameter)
instead of
public HomeController(string parameter)
and
ObjectFactory.Initialize(x => x.For<logger>().Use<logger>());
instead of
ObjectFactory.Initialize(x => x.For<string>().Use<string>());
it works probably but with the string parameter it does not work .
so please couold anyone explain that for me.
From my understanding of StructureMap (and someone please correct me if I'm wrong) the reason you're seeing the behaviour that you're seeing is because StructureMap will use the longest constructor by default and attempt to fill in the parameters with the default instance registered with StructureMap.
In your instance you're not providing a default instance of string so StructureMap doesn't know how to resolve it.
If you wish to do what you're trying to do then your best bet is to look at creating a custom convention (see this answer for more information), however these do rely on knowing the name of the property your constructor is expecting.
Generally though, when dealing with strings your best bet is to move the string to an intermediate type and inject that instead.

How to add web API to an existing MVC Hottowel project

I have one Hottowel project created using it's template from Visual Studio. I want to add the Web API feature in that project. I have created a Web Api controller to the controller folder and tries to access like "http://localhost:53397/api/Values" But I get an error saying The resource cannot be found error.
My controller code looks like below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MvcApplication8.Controllers
{
public class ValuesController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(int id)
{
return "value";
}
// POST api/<controller>
public void Post([FromBody]string value)
{
}
// PUT api/<controller>/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/<controller>/5
public void Delete(int id)
{
}
}
}
I have the cs file in APP_start folder called BreezeWebApiConfig.cs which contains the logic to map the route like below.
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "api/{controller}/{action}"
);
Let me know If I am missing any configuration setting for Web APi.
Try to decorate your ApiController like bellow :
[BreezeController]
public class NorthwindIBModelController : System.Web.Http.ApiController {
readonly EFContextProvider<NorthwindIBContext> ContextProvider =
new EFContextProvider<NorthwindIBContext>();
[HttpGet]
public String Metadata() {
return ContextProvider.Metadata();
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle) {
return ContextProvider.SaveChanges(saveBundle);
}
[HttpGet]
public IQueryable<Customer> Customers() {
return ContextProvider.Context.Customers;
}
For more information have a look to breeze documentation here.
Its seems like you are making a wrong Url Request. Look at your breeze route configuration for WebApi. You need to Pass like that http://localhost:53397/api/Values/Get as breeze is using Controller action based routing.
Hope this will help.