MVC 4 How to get json response from a repository into a view using Ajax? - asp.net-mvc-4

I am a newbie when it comes to MVC4 Web Development and there's something I am struggling with.
Basically, I have the following :
public class maincontroller: Controller
{
private MyRepository myRepository;
public mainController()
{
myRepository= new MyRepository();
}
public ActionResult Index()
{
var mystuff = myRepository.GetPrograms();
return View(mystuff);
}
public ActionResult MyStuff()
{
var mystuff = myRepository.GetStuff(1);
return Json(mystuff , JsonRequestBehavior.AllowGet);
}
}
Assuming that in my `MyRepository' class I have two functions:
One that is setting up `mystuff':
public MyRepository()
{
for (int i = 0; i < 8; i++)
{
programs.Add(new MyStuff
{
Title = "Hello" + i,
content = "Hi"
});
}
}
and second function that gets Stuff:
public List<MyStuff> GetStuff(int pageNumber = 0)
{
return stuff
.Skip(pageNumber * pageCount)
.Take(pageCount).ToList();
}
All works well. I mean I am able to iterate through `stuff' and display on a view...
The problem is that I want to display MyStuff() ( which returns Json ) using AJAX and then append all stuff to a view. How do I do that?
I have been beating my head against the wall for about 4 hours now, and can't get this working.
Please any help will be much appreciated.
Thank you.

At the most straightforward level, you can simply append HTML to your document using something like this (assuming you're using JQuery, because it's so much easier):
<div id="container"></div>
// make AJAX call to "MyStuff" action in the current controller
$.get(#Url.Action("MyStuff", function(data) {
// cycle through each item in the response
$.each(data, function(index, item) {
// construct some HTML from the JSON representation of MyStuff
var html = "<div>" + item.StuffProperty + "</div>";
// append the HTML to a container in the current document
$("#container").append(html);
});
});
This adds some HTML for each item in the collection to a container element, using (eg) StuffProperty from the MyStuff class.
Appending HTML manually like this can be a hassle once it gets too complicated -- at that point you should consider using either:
Partial views (return HTML directly from the controller, instead of JSON)
A client-side templating engine like Mustache.js, Underscore.js, etc, to convert JSON into HTML.

Related

Is it possible to render View based on OData query?

I've got Asp.Net Core application and there is an abstract controller.
I want to create a method, which will allow me to render list of entities as PartialView.
I've made it like this:
Should return PartialView with list of entities
[HttpGet]
[EnableQuery()]
public async Task<IActionResult> _List()
{
var result = _context.GetQueryByType<T>(); //returns DbSet<T> of whole table
return PartialView(await result.ToListAsync());
}
Example PartialView
#model IEnumerable<SomeClass>
<table class="table table-sm table-striped table-hover">
...
</table>
I want to call my method like this:
http://localhost:32769/SomeController/_List?$filter=id%20eq%2009515a38-2a1a-4a53-a4f8-e91e4dbd870b
And get filtered List view.
But anyway I get only whole table data.
The only solution for me is split this logic into 2 methods:
Get filtered Json data via standard Odata methods like:
http://localhost:32769/odata/SomeClass?$filter=ReleaseId%20eq%2011f28258-48cb-4c82-85e0-822850fd1f5c
Pass this data to method:
[HttpPost]
public IActionResult _List([FromBody] IEnumerable<T> entities)
{
return PartialView(entities);
}
I don't like this solution. Is there any possibility to filter my view data using OData queries?
Thx to ChristophLütjen.
.ApplyTo() is the solution.
Finally, working method looks like:
[HttpGet]
[EnableQuery]
public async Task<IActionResult> _List(ODataQueryOptions<T> queryOptions)
{
var result= (IQueryable<T>)queryOptions.ApplyTo(_context.GetQueryByType<T>());
return PartialView(await result.ToListAsync());
}
Also, it's very important to use ODataQueryOptions<T>, not ODataQueryOptions.
If you will use not common class, you will get an error, that method should return IEnumerable<T>, but not IActionResult.
Here is some documentation. Just want to pin it to the answer.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnet.odata.query.odataqueryoptions?view=odata-aspnetcore-7.0
Hope, that this info will be usefull for someone else.
upd:
Also I've found out, that it's not perfect soulution, if you want to use $expand method in your OData queries.
If you'll try to get type T of expanded query, you'll face the problem of SelectAllAndExpand type.
In this case this is the solution, I think it's not very beatiful and perfect, but it works:
[HttpGet]
[EnableQuery]
public IActionResult _List(ODataQueryOptions<T> queryOptions)
{
var validationSettings = new ODataValidationSettings
{
AllowedQueryOptions = AllowedQueryOptions.All,
AllowedFunctions = AllowedFunctions.All,
};
queryOptions.Validate(validationSettings);
IQueryable resultSet = queryOptions.ApplyTo(_context.GetQueryByType<T>(), new ODataQuerySettings());
List<T> resultList = new List<T>();
foreach (var item in resultSet)
{
if (item is T)
{
resultList.Add((T)item);
}
else if (item.GetType().Name == "SelectAllAndExpand`1")
{
var entityProperty = item.GetType().GetProperty("Instance");
resultList.Add((T)entityProperty.GetValue(item));
}
}
return PartialView(resultList as IEnumerable<T>);
}
Found it here: https://github.com/OData/WebApi/issues/1441

How can I change css directly(without variable) in Blazor?

I am using the server-side of Blazor.
I want to change the CSS of the body.
In Jquery I can write the code like this easily:
$("body").css("overflow-y","hidden");
However, with this tutorial(Blazor Change Validation default css class names) said, it seems I can only change the CSS by changing the class name.
It is so complex while crossing the component, especially the body is at the top of all the components.
I wonder whether there is a way can changes CSS directly in Blazor. Thank you.
There are several ways of getting out of the "blazor way" of doing things and accomplishing css modification of an element.
Simplest: Just like you can use the class attribute, use the style attribute
<element style=#myStyle></element>
#code {
string myStyle;
void MyMethod() {
myStyle="overflow-y: hidden;"
}
}
Advanced: Use JS interop
a. In the main view (index.html or Pages/_Host.cshtml depending on project type), create a js endpoint for your component
<script>
window.applyStyleForElement = function(styleOp) {
document.getElementById(styleOp.id).style[styleOp.attrib] = styleOp.value;
}
</script>
b. In razor file:
#Inject IJRRuntime JSRuntime
<element id=#myId></element>
#code {
string myId = Guid.NewGuid().ToString("n");
async Task MyMethod() {
await JSRuntime.InvokeAsync("applyStyleForElement",
new { id = myId, attrib = "overflowY", value = "hidden" });
}
}
Finally, applying to your special case with body element ("advanced" method above).
a. In the main view (index.html or Pages/_Host.cshtml depending on project type), create a js endpoint
<script>
window.applyStyleForBody = function(style) {
document.body.style[style.attrib] = style.value;
}
</script>
b. In razor file:
#Inject IJRRuntime JSRuntime
(...)
#code {
async Task MyMethod() {
await JSRuntime.InvokeAsync("applyStyleForBody",
new { attrib = "overflowY", value = "hidden" });
}
}
Well, Blazor does not support direct css modification yet, since Web Assembly doesn't. Anyway heads up, it is on the road-map for Web Assembly/Blazor.
Therefor your best bet is, changing the class name with variables. At least for now.
I'M NOT SURE IT'S THE RECOMMENDED WAY BUT it works!
For one of my project, I include a in the page html itself, using params :
<style>
html
{
background-color:#_ColorCss;
}
</style>
//html stuff here
#code
{
public string Color{ get; set; } = "white";
string _ColorCss => $"{Color}"; //use this in case of formatting (ex : add 'px' or that kind of things)
//code stuff here
}
not the very sexiest way but it works
have fun !
Well, actually there is a way to do that and it works really good (it might suffer a little delay though).
I know this answer is a little bit late but it might help other people who face the same challenge.
We need to create some JS code that includes the wanted files:
function includeLeftStyle() {
appendStyle("left.css");
}
function includeRightStyle() {
appendStyle("right.css");
}
function appendStyle(path) {
var element = document.createElement("link");
element.setAttribute("rel", "stylesheet");
element.setAttribute("type", "text/css");
element.setAttribute("href", path);
document.getElementsByTagName("head")[0].appendChild(element);
}
The wished CSS can be called according to the language (any other coditions) in the MainLayout:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (// is left)
{
await JSRuntime.InvokeAsync<object>("includeLeftStyle");
}
else
{
await JSRuntime.InvokeAsync<object>("includeRightStyle");
}
}
}
Happy coding! :)
Beginnig from the #Tewr answer, we can also change the whole class:
a) In the main view (index.html or Pages/_Host.cshtml depending on project type), create a js endpoint
<script>
window.applyStyleForElement = function (styleOp) {
if (styleOp != null) {
document.getElementById(styleOp.id).className = styleOp.value;
}
}
</script>
b) Then in the razor file
async Task MyMethod(string sortColumn)
{
await JsRuntime.InvokeVoidAsync("applyStyleForElement",
new { id = sortColumn, value = "newClassName" });
}

Implement Pagination in ASP.NET Core 2.1 Web API

I searched, but did't really found articles on how to implement pagination logic in an ASP.NET WebAPI Core 2.1 application...
I have the following
[Route("api/[controller]")]
[ApiController]
[EnableCors("AllowMyOrigin")]
public class EntriesController : ControllerBase
{
private readonly EntriesContext _context;
public EntriesController(EntriesContext context) {
_context = context;
if (_context.Entries.Count() == 0) {
_context.Entries.Add(new Entry { From = "default", To = "default" });
_context.SaveChanges();
}
}
[HttpGet]
public ActionResult<List<Entry>> GetAll() {
return _context.Entries.ToList();
}
[HttpGet("{id}", Name = "GetEntry")]
public ActionResult<Entry> GetById(long id) {
var item = _context.Entries.Find(id);
if (item == null) { return NotFound(); }
return item;
}
Now, I want my entries to be paginated using new params page and pageSize. Say
/api/entries?pageSize=3&page=2
Should I use the GetAll() method by adding some http params to it, or rather create a new method? There are no much sense to use page without pageSize, how do I manage this?
There are libraries, such as X.PagedList you can use. Frankly, pagination is pretty straight-forward, so you may not even need that. All you need to know is the page number, page size, and total number of results. The page number obvious comes from the request, and the page size can as well, if you want it customizable, or you can hard-code it.
public ActionResult<List<Entry>> GetAll(int page = 1, int size = 10)
Then, you can use Skip and Take to get the data for a particular page:
var query = _context.Entries;
var entries = await query.Skip((page - 1) * size).Take(size).ToListAsync();
var count = await query.CountAsync();
Then, all you need to know is the total number of pages, which can be calculated simply via:
var totalPages = (int)Math.Ceil(count / (float)size);
From that, you can calculate anything else you need, i.e.:
var firstPage = 1; // obviously
var lastPage = totalPages;
var prevPage = page > firstPage ? page - 1 : firstPage;
var nextPage = page < lastPage ? page + 1 : lastPage;
First of all, you can default you pageSize value to something:
[HttpGet]
public ActionResult<List<Entry>> GetAll(int? page = null, int? pageSize = 10)
{
if (!page.HasValue) {
return _context.Entries.ToList();
}
// do you pagination here
}
But you also may look at OData, it seems to be your case. It will allow you to query your data using http params, e.g.: /api/Entires?$skip=5&$top=5
I've just created a PagingTagHelper for ASP.NET Core Razor pages to render paging control easily with just the basic parameters, the simplest setup looks like below:
<paging total-records="Model.TotalRecords" page-no="Model.PageNo">
</paging>
all you need is to provide total records and page number for it to run. The default query string parameters are "p" for page number and "s" for page size, however, it is customizable/localizable, you can change all the settings according to your own requirements.
you can install it from nuget:
Install-Package LazZiya.TagHelpers -Version 1.0.2
then you need to add the tag helper to the _ViewImports.cshtml file:
#addTagHelper *, LazZiya.TagHelpers
http://ziyad.info/en/articles/21-Paging_TagHelper_for_ASP_NET_Core
more documentations and live demo will be available soon.

Asp.net core Custom routing

I am trying to implement custom routing on an asp.net core application.
The desired result is the following:
http://Site_URL/MyController/Action/{Entity_SEO_Name}/
Entity_SEO_Name parameter will be a unique value saved into the database that it is going to help me identify the id of the entity that I am trying to display.
In order to achieve that I have implemented a custom route:
routes.MapMyCustomRoute(
name: "DoctorDetails",
template: " {controller=MyController}/{action=TestRoute}/{name?}");
public class MyTemplateRoute : TemplateRoute
{
public override async Task RouteAsync(RouteContext context)
{
//context.RouteData.Values are always empty. Here is the problem.
var seo_name = context.RouteData.Values["Entity_SEO_Name"];
int entityId = 0;
if (seo_name != null)
{
entityId = GetEntityIdFromDB(seo_name);
}
//Here i need to have the id and pass it to controller
context.RouteData.Values["id"] = entityId;
await base.RouteAsync(context);
}
}
My controller actionresult:
public ActionResult TestRoute(int id)
{
var entity = GetEntityById(id);
return Content("");
}
The problem with this approach is that the context.RouteData.Values are always empty.
Any ideas on how to move forward with this one ?
Your solution too complicated. You can have route template like
template: "{controller=Home}/{action=Index}/{seo?}"
and controller action just like
public ActionResult TestRoute(string seo)
{
var entity = GetEntityBySeo(seo);
return Content("");
}
It is enough, asp.net mvc is smart enough to bind seo variable to the parameter from url path.

Naming conventions for view pages and setting controller action for view

I am unsure on how I should be naming my View pages, they are all CamelCase.cshtml, that when viewed in the browser look like "http://www.website.com/Home/CamelCase".
When I am building outside of .NET my pages are named like "this-is-not-camel-case.html". How would I go about doing this in my MVC4 project?
If I did go with this then how would I tell the view to look at the relevant controller?
Views/Home/camel-case.cshtml
Fake edit: Sorry if this has been asked before, I can't find anything via search or Google. Thanks.
There are a few ways you can do this:
Name all of your views in the style you would like them to show up in the url
This is pretty simple, you just add the ActionName attribute to all of your actions and specify them in the style you would like your url to look like, then rename your CamelCase.cshtml files to camel-case.cshtml files.
Use attribute routing
Along the same lines as above, there is a plugin on nuget to enable attribute routing which lets you specify the full url for each action as an attribute on the action. It has convention attributes to help you out with controller names and such as well. I generally prefer this approach because I like to be very explicit with the routes in my application.
A more framework-y approach
It's probably possible to do something convention based by extending the MVC framework, but it would be a decent amount of work. In order to select the correct action on a controller, you'd need to map the action name on its way in to MVC to its CamelCase equivalent before the framework uses it to locate the action on the controller. The easiest place to do this is in the Route, which is the last thing to happen before the MVC framework takes over the request. You'll also need to convert the other way on the way out so the urls generated look like you want them to.
Since you don't really want to alter the existing method to register routes, it's probably best write a function in application init that loops over all routes after they have been registered and wraps them with your new functionality.
Here is an example route and modifications to application start that achieve what you are trying to do. I'd still go with the route attribute approach however.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
WrapRoutesWithNamingConvention(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
private void WrapRoutesWithNamingConvention(RouteCollection routes)
{
var wrappedRoutes = routes.Select(m => new ConventionRoute(m)).ToList();
routes.Clear();
wrappedRoutes.ForEach(routes.Add);
}
private class ConventionRoute : Route
{
private readonly RouteBase baseRoute;
public ConventionRoute(RouteBase baseRoute)
: base(null, null)
{
this.baseRoute = baseRoute;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var baseRouteData = baseRoute.GetRouteData(httpContext);
if (baseRouteData == null) return null;
var actionName = baseRouteData.Values["action"] as string;
var convertedActionName = ConvertHyphensToPascalCase(actionName);
baseRouteData.Values["action"] = convertedActionName;
return baseRouteData;
}
private string ConvertHyphensToPascalCase(string hyphens)
{
var capitalParts = hyphens.Split('-').Select(m => m.Substring(0, 1).ToUpper() + m.Substring(1));
var pascalCase = String.Join("", capitalParts);
return pascalCase;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var valuesClone = new RouteValueDictionary(values);
var pascalAction = valuesClone["action"] as string;
var hyphens = ConvertPascalCaseToHyphens(pascalAction);
valuesClone["action"] = hyphens;
var baseRouteVirtualPath = baseRoute.GetVirtualPath(requestContext, valuesClone);
return baseRouteVirtualPath;
}
private string ConvertPascalCaseToHyphens(string pascal)
{
var pascalParts = new List<string>();
var currentPart = new StringBuilder();
foreach (var character in pascal)
{
if (char.IsUpper(character) && currentPart.Length > 0)
{
pascalParts.Add(currentPart.ToString());
currentPart.Clear();
}
currentPart.Append(character);
}
if (currentPart.Length > 0)
{
pascalParts.Add(currentPart.ToString());
}
var lowers = pascalParts.Select(m => m.ToLower());
var hyphens = String.Join("-", lowers);
return hyphens;
}
}
}