I have a strange one here. I've been porting some controllers over from asp.net odata to asp.net core odata and ran into a bit of a snag with the primary keys.
In my .net framework 4.6.2 app I have a GUID as a primary key and in the .net core app I have a string as a primary key. I've been able to get most everything working EXCEPT a Get(key) method. This is my method signature:
[HttpGet]
[EnableQuery]
public async Task<IActionResult> Get([FromODataUri] string key)
{
// key is null!
}
Please Below Follow step
Install Microsoft.AspNetCore.OData from NuGet Package
StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddOData(); //This Is added for OData
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc(routeBuilder =>
{
routeBuilder.EnableDependencyInjection(); //This Is added for OData
routeBuilder.Expand().Select().Count().OrderBy().Filter(); //This Is added for OData
});
}
In Api Conteroller
[HttpGet]
[EnableQuery] //This Is added for OData
public ActionResult<List<TestModel>> Get()
{
var model = new List<TestModel>();
for (int i = 1; i <= 10; i++)
{
var res = new TestModel()
{
ID = i,
Name="Test"+i,
Mobile="Test"+i,
City="Test_"+i
};
model.Add(res);
}
return model;
}
public class TestModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Mobile { get; set; }
public string City { get; set; }
}
After Run Api And Check Like This
Related
I am working on a ASP.NET Web Api and I'm having an issue figuring out how to display data to the api.
So to explain what I need to figure out. After accessing the objects from the Json file and converting JSON to a .NET type. I believe I should be able to display these on the api?
For example, whatever field I ask for, it should return the result sorted by that field.
Now I need to display this data in different ports. For example.
https://host:port/api/books returns all unsorted (Book1-Book13)
https://host:port/api/books/id returns all sorted by id (Book1-Book13)
https://host:port/api/books/id/1 returns all with id containing '1' sorted by id (Book1, Book10-13)
I have followed this tutorial by microsoft and the application seems to be working in regards to starting and using different ports.
This is the code I have set up in my controller class BooksController.cs
[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{
[HttpGet]
public ActionResult GetAllBooks()
{
string[] books = { "book1", "book2", "book3" };
if (!books.Any())
return NotFound();
return Ok(books);
}
[HttpGet("id/{id}")]
public string GetBookById(int id)
{
return $"book: {id}";
}
[HttpGet("author/{author}")]
public string GetBookByAuthor(string author)
{
return $"author: {author}";
}
This is my startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
GetAllBooks();
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TodoContext>(opt =>
opt.UseInMemoryDatabase("TodoList"));
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
public void GetAllBooks()
{
string _path = $"C:\\Users\\filip\\source\\repos\\NexerGroupApi\\NexerGroupApi\\books.json";
string jsonFromFile;
using (var reader = new StreamReader(_path))
{
jsonFromFile = reader.ReadToEnd();
}
var booksFromJsonFile = JsonConvert.DeserializeObject<List<NexerGroupApi.Book>>(jsonFromFile);
JArray bookArray = JArray.FromObject(booksFromJsonFile);
IList<Book> bookList= bookArray.Select(p => new Book
{
Id = (string)p["Id"],
Author = (string)p["Author"],
Title = (string)p["Title"],
Genre = (string)p["Genre"],
Price = (string)p["Price"],
Publish_date = (string)p["Publish_date"],
Description = (string)p["Description"]
}).ToList();
}
public class Book
{
public string Id { get; set; }
public string Author { get; set; }
public string Title { get; set; }
public string Genre { get; set; }
public string Price { get; set; }
public string Publish_date { get; set; }
public string Description { get; set; }
}
I wonder where I actually set up my GetAllBooks() method so that I can then access them in the BooksController.cs and be able to do api/books/bookNameFromJsonFile and show that specific object with all of it's contents such as id, author, title, genre, etc.
I know that my IList<Book> bookList in the class startup.cs does not work, for some reason every variable get's null value. Although not sure why.
As the error suggests, you have two routes that are ambiguous [HttpGet("{id}")] and [HttpGet("{author}")]
Given
When you hit api/books/1 the 1 could be the author or the bookid.
Consider changing the route of the author method to:
[HttpGet("author/{author}")]
[HttpGet("{id}")]
public string GetBookById(int id)
{
return $"book: {id}";
}
[HttpGet("author/{author}")]
public string GetBookByAuthor(string author)
{
return $"author: {author}";
}
So requests would route as follows:
api/books/1 would invoke GetBookById
api/books/author/jsmith would invoke GetBookByAuthor
Edit: Add list filtering:
To find a Book from the List<Book> then this can be done:
private readonly List<Book> _bookList; //populate this with books from json file
[HttpGet("{id}")]
public IActionResult GetBookById(int id)
{
var book = GetBookObjectById(id);
if(book is null) return NotFound();
return Ok(book);
}
private Book GetBookObjectById(int id)
{
return _bookList.FirstOrDefault(book => book.Id == id);
}
private Book GetBookObjectByAuthor(string author)
{
return _bookList.FirstOrDefault(book => book.Author == author);
}
I'm trying to implement pagination with asp.net core 2.2 and Microsoft.AspNetCore.OData 7.1.0 with following configuration:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(b =>
{
b.EnableDependencyInjection();
});
}
}
For this I have a testing controller:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[EnableQuery(PageSize = 5)]
public IQueryable<int> Get()
{
return new int[] { 1,2,3,4,5,6,7,8,9,10 }.AsQueryable();
}
}
When invoking an endpoint, I would expect a response like:
{
"#odata.context":...,
"value":[1,2,3,4,5],
"#odata.nextLink":...
}
but instead I get only:
[1,2,3,4,5]
So how do I get those extra #odata properties?
Finally I figured out how to do it.
First it doesn't work with primitive types so I had to create strong one with Id property:
public class Value
{
public Value(int id)
{
Id = id;
}
public int Id { get; set; }
}
Second I had to drop ApiController and Route attributes from controller .
public class ValuesController : ControllerBase
{
[HttpGet]
[EnableQuery(PageSize = 5)]
public IQueryable<Value> Get()
{
return Enumerable.Range(1, 10).Select(i => new Value(i)).AsQueryable();
}
}
And finally register odata endpoint:
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Value>("Values");
app.UseOData("odata", "api", builder.GetEdmModel());
I have a new OData V4 service that I'm trying to get running and I'm seeing the unexpected error...
"The property 'ProductType' cannot be used in the $expand query
option."
I didn't have problems with this in another OData service and I've been comparing the two and I can't find a significant difference between the two WRT the setup of the items in the model and the WebApiConfig. I built this following the example laid out in the article create-an-odata-v4-endpoint whereas the other one was created with a scaffolding wizard.
So here's the layout of the tables, the controllers, and the WebApiConfig. Where else do I look for the reason behind the failure to relate?
// Product.cs
public partial class Product
{
public int ProductId { get; set; }
public int ProductTypeId { get; set; }
public string Size { get; set; }
public string PartNo { get; set; }
public virtual ProductType ProductType { get; set; }
}
// ProductType.cs
public partial class ProductType{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")] public ProductType()
{
this.Products = new HashSet<Product>();
}
public int ProductTypeId { get; set; }
public string ProductTypeName { get; set; }
public string Image { get; set; }
public string ProductDescription { get; set; }
public string InstallInstructions { get; set; }
public string DataSheet { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Product> Products { get; set; }
}
// WebApiConfig.cs
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 }
);
config.SetTimeZoneInfo(TimeZoneInfo.Utc);
ODataConventionModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Product>("Products");
builder.EntityType<Product>().HasKey(entity => entity.ProductId);
builder.EntityType<Product>().HasRequired(entity => entity.ProductType, (entity, targetEntity) => entity.ProductTypeId == targetEntity.ProductTypeId);
builder.EntitySet<ProductType>("ProductTypes");
builder.EntityType<ProductType>().HasKey(entity => entity.ProductTypeId);
builder.EntityType<ProductType>().HasMany(entity => entity.Products);
config.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());
config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
// ProductTypesController.cs
public class ProductTypesController : BaseController
{
[EnableQuery]
public IQueryable<ProductType> Get()
{
return db.ProductTypes;
}
[EnableQuery]
public SingleResult<ProductType> Get([FromODataUri] int key)
{
IQueryable<ProductType> result = db.ProductTypes.Where(p => p.ProductTypeId.Equals(key));
return SingleResult.Create(result);
}
....
}
// ProductsController.cs
public class ProductsController : BaseController
{
[EnableQuery]
public IQueryable<Product> Get()
{
return db.Products;
}
[EnableQuery]
public SingleResult<Product> Get([FromODataUri] int key)
{
IQueryable<Product> result = db.Products.Where(p => p.ProductId.Equals(key));
return SingleResult.Create(result);
}
...
}
I've tried both directions with of referencing the relate item with singular and multiple selection (typing these urls into the address bar):
/ProductTypes?$expand=Products, and /ProductTypes(3)?$expand=Products, and /Products?$expand=ProductType, and /Products(3)?$expand=ProductType
I get that same error in each case.
If there's something else needed to determine the cause I'll be happy to look it up, I just need to know where to look.
Thanks, Mike
I found the answer in a post in the odata github issues. issuecomment-248168536 According to this comment this is new behavior in Microsoft.AspNet.OData package version 6.0.0 and is a breaking change.
I went back to the other service that was working and checked out the Nuget package and it has v 5.6.0 installed rather than the latest (which is 7.1.0 currently).
So the fix is to add the config line for the features you want before the mapping...
config.Expand().Select();
config.MapODataServiceRoute("odata", null, builder.GetEdmModel());
This is the fix for enabling the option globally to make it work just like the < v6.0 versions. There's a document referenced in that thread (13-01-modelbound-attribute) that demonstrates the new feature of granular control of the options through model attributes.
HTH, Mike
I'm working on my first application in .Net Core.
I'm getting this build error for some reason:
Error CS7036 There is no argument given that corresponds to the required formal parameter 'options' of 'LakeViewContext.LakeViewContext(DbContextOptions<LakeViewContext>)' LakeView
I wasn't able to find a solution through Google Search or MS documentation.
My Context class:
using LakeView.Models;
using Microsoft.EntityFrameworkCore;
namespace LakeView
{
public class LakeViewContext : DbContext
{
public LakeViewContext(DbContextOptions<LakeViewContext> options) : base(options)
{
}
public DbSet<HTMLElement> HTMLElements { get; set; }
public DbSet<CustomizedElement> CustomizedElements { get; set; }
public DbSet<TemplateFileType> TemplateFileTypes { get; set; }
public DbSet<StyleFile> StyleFiles { get; set; }
public DbSet<Template> Templates { get; set; }
public DbSet<Course> Courses { get; set; }
public DbSet<Page> Pages { get; set; }
public DbSet<HTML> HTMLs { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CustomizedElementTemplate>()
.HasKey(s => new { s.CustomizedElementId, s.TemplateId });
base.OnModelCreating(modelBuilder);
}
}
}
Controller class:
using LakeView.Models;
using LakeView.Models.ViewModels;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
namespace LakeView.Controllers
{
public class CoursesController : Controller
{
private LakeViewContext db = new LakeViewContext();
public IActionResult Index()
{
ICollection<Course> courses = db.Courses.ToList();
return View(courses);
}
[HttpGet]
public IActionResult CreateCourse()
{
return View("CreateCourse");
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateCourse(CreateCourseViewModel courseVM)
{
if (ModelState.IsValid)
{
Course newCourse = new Course()
{
CourseCode = courseVM.CourseCode,
CourseTitle = courseVM.CourseTitle,
MasterOU = int.Parse(courseVM.MasterOU)
};
db.Courses.Add(newCourse);
db.SaveChanges();
return RedirectToAction("Index");
}
return View("CreateCourse", courseVM);
}
}
}
(bold text is underlined in Visual Studio with the same error
"private LakeViewContext db = new LakeViewContext();"
Startup class:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using LakeView.Models;
namespace LakeView
{
public class Startup
{
// 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.AddMvc();
var connection = #"Data Source = (localdb)\MSSQLLocalDB; Database = LakeViewData; Trusted_Connection = True;";
services.AddDbContext<LakeViewContext>(options => options.UseSqlServer(connection));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
}
}
LakeViewContext expects a DbContextOptions<LakeViewContext> to be passed into its constructor. However, you are calling the constructor without providing anything:
private LakeViewContext db = new LakeViewContext();
To fix the issue, you can just plug into the Dependency Injection system that you've set up. To do this, change your controller as follows:
public class CoursesController : Controller
{
private readonly LakeViewContext db;
public CoursesController(LakeVieContext db)
{
this.db = db;
}
...
The ASP.NET Core Dependency Injection system will provide you with a LakeViewContext in the constructor - Just use that.
you are trying to new up the dbcontext in your controller without passing in the options.
You should instead add a constructor to your controller and add the dbContext to your constructor so it will get injected, ie
public CoursesController(LakeViewContext dbContext)
{
db = dbContext;
}
private LakeViewContext db;
... the rest of your code
dependency injection will pass it in to you
When observing at how the classes are generated via the database first approach, I was able to fix this error with an empty constructor.
public class MyDBContext : DbContext
{
public MyDBContext()
{
}
//Rest of your code
}
Long story, originally a project was started using Azure AD auth as the authentication for the application, approaching the end of the project we noticed the huge flaws Azure auth had (Normal Users where not able to authenticate)
So the decision was made to move from Azure auth to ASP Identity Auth. To do this a new web project was created and all of the data from the first project was placed into it by coping the files from one project to another.
This caused a few headaches but eventually got everything going. With the new project it came with a Migration and ApplicationDBContext already to handle all of the ASP tables. This was the first issue as we already had a very detailed DbContext but using command line we managed to run all the migrations correctly.
Now we are presented with a new issue, every time we launch the application in VS2015 the error:
The Error:
InvalidOperationException: Unable to resolve service for type 'Musted.Models.ApplicationDbContext' while attempting to activate 'Microsoft.AspNet.Identity.EntityFramework.UserStore`3[Musted.Models.ApplicationUser,Microsoft.AspNet.Identity.EntityFramework.IdentityRole,Musted.Models.ApplicationDbContext]'.
I believe the issue is somewhere in the Startup.cs with how the identity is created.
Existing Context: ProjectContext.cs
New ASP Auth Context: ApplicationDBContext.cs
Startup.cs <-> Where I believe the issue to be
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ProjectContext>();
services.AddScoped<ProjectContextSeedData>();
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, MusteredContextSeedData seeder)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
try
{
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
.CreateScope())
{
serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
.Database.Migrate();
}
}
catch { }
}
app.UseIISPlatformHandler(options => options.AuthenticationDescriptions.Clear());
app.UseStaticFiles();
app.UseIdentity();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
ApplicationDBContext.cs
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connString = Startup.Configuration["Data:ProjectContextConnection"];
optionsBuilder.UseSqlServer(connString);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
}
ProjectContext.cs
public class ProjectContext: DbContext
{
public ProjectContext()
{
Database.EnsureCreated();
}
public DbSet<User> Users {get; set;}
public DbSet<UserLevel> UserLevels { get; set; }
public DbSet<ApprovalStatus> ApprovalStatus { get; set; }
public DbSet<Area> Areas { get; set; }
public DbSet<Company> Company { get; set; }
public DbSet<Event> Events { get; set; }
public DbSet<LeaveType> LeaveTypes { get; set; }
public DbSet<Leave> Leave { get; set; }
public DbSet<Shift> Shifts { get; set; }
public DbSet<UserLoginAudit> UserLoginAudit { get; set; }
public DbSet<UserType> UserType { get; set; }
public DbSet<ShiftDay> ShiftDay { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connString = Startup.Configuration["Data:ProjectContextConnection"];
optionsBuilder.UseSqlServer(connString);
base.OnConfiguring(optionsBuilder);
}
}
UPDATE
So after some more digging, I have found the line in the ASP generated code that is failing.
private readonly UserManager<ApplicationUser> _userManager;
//This is the line that is erroring
var result = await _userManager.CreateAsync(user, model.Password);
That calls is all throughout the ASP stuff so I am guessing nothing will work
In the end - I deleted the project and imported the classes from the start. I wasted hours trying to get it working the other way and the new project was just much quicker.