Asp.net MVC binding to view model - c#

Current Situation: Starting with ASP.net MVC development and coming from MVVM app development I'm struggling with the binding of a view with view model / controller. I started with an empty project and tried to create model, viewmodel, controller and view. Starting the project I get a "500 Internal server error" but don't understand what's wrong (no error in the output window). I just can't understand how a view actually binds to a view model (probably because I think too much in MVVM).
What I currently have:
Startup.cs:
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace WebApplication1
{
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 http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
app.UseIISPlatformHandler();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Sample}/{action=Index}/{id?}");
});
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
}
Model:
using System;
namespace WebApplication1.Models
{
public class SomeModel
{
public string Title { get; set; }
public DateTime Time { get; set; }
}
}
ViewModel:
using System;
using System.Collections.Generic;
using WebApplication1.Models;
namespace WebApplication1.ViewModels
{
public class SampleViewModel
{
public IList<SomeModel> SomeModels { get; set; }
public SampleViewModel()
{
SomeModels = new List<SomeModel>();
SomeModels.Add(new SomeModel()
{
Time = DateTime.Now,
Title = "Hallo"
});
}
}
}
Controller:
using Microsoft.AspNet.Mvc;
using WebApplication1.ViewModels;
namespace WebApplication1.Controllers
{
public class SampleController : Controller
{
//
// GET: /Sample/
public IActionResult Index()
{
return View(new SampleViewModel());
}
}
}
View:
#model WebApplication1.ViewModels.SampleViewModel
<!DOCTYPE html>
<html>
<head>
<title>Hallo</title>
</head>
<body>
#foreach (var someModel in Model.SomeModels)
{
<div>#someModel.Title</div>
}
</body>
</html>
I found a lot of articles talking about model binding but they only talk about forms and input. What I want is to show some data, e.g. from a database in some kind of list or so and therefore don't need to post any date to the website.
Does anyone see the (probably obvious) issue in my sample code?
The project is based on ASP.net 5 MVC 6 using DNX.
I already set some breakpoints to see whether the controller is actually called. And it is. I went through the few methods with the debugger without any issue. Also, the output window does not show any error or sth. like that.

The view name was missing in the result for the GET method. So instead of
return View(new SampleViewModel());
it must be
return View("Sample", new SampleViewModel());
I thought that the connection between view and controller is purely convention based. So a controller named Sample searches for a view named Sample in a folder called Sample which in turn is a subfolder of Views.
Not sure exactly why, but it works this way.

Related

Api for ASP.NET Web Application (WebForms) Project showing 400

I'm new to the .NET framework and my company doesn't use Core yet, so I'm trying to figure out why my web application api is showing a 400. I had a normal web forms project and added a controller class named TagController.cs. My project is on port 44318 and I've tried accessing localhost/44318/api/tag with no luck. I also tried adding a controllers folder with api sub folder and the controller inside it, but to no avail. I've posted images of my project hierarchy and the errors themselves. I have a feeling that the project not having a global.asax could have something to do with it, but there is one in another project. Maybe TagController.cs is pointing to another port? Any help is greatly appreciated.
TagController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using ClarityDBWebFormsRedis;
using StackExchange.Redis;
namespace ClarityDBWebFormsRedis
{
public class TagController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/<controller>/5
public string Get(string data) {
return "doge";
}
// 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)
{
}
}
}
You need a default (route) configuration in the project, so that it knows what it should do with the ApiControllers, or how the API can be called. This is defined for example in the Global.asax. You can simply put your class TagController into a folder called "Controllers".
The Global.asax looks then accordingly e.g. like this:
using System.Web.Http;
using System.Web.Routing;
(...)
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHttpRoute(name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional });
}
An ApiController for example looks like this:
public class PingController : ApiController
{
[HttpGet, AllowAnonymous]
public IHttpActionResult Get()
{
return Ok();
}
}
For normal pages it is enough to create an .Aspx page and then call it in the browser according to the created folder structure. If you use MVC, then this page is created in different files and folders in the project (Views/Home.cshtml, Models/HomeViewModel.cs and Controllers/HomeController.cs).

WEB API validation class using attributes

I have been searching google whole day and I cannot find the answer:
the problem is to validate a class with the attributes when I create a class.
So it goes: I read a POST request body and each field should be validated.
It's deserialized from Json to Request class. Then this class has its requirements.
Is it possible to do this in asp.net core using attributes?
As far as I know there are 2 ways to check class: using ValidationAttribute and Attribute inheritance.
I could swear that some time ago I was able to debug it and go to Validation class, but now it seems that is only regarding some client validation and it does not validate in backend middleware.
The last thing I am trying is using Validator.TryValidateObject.
Is it better option?
I could swear that some time ago I was able to debug it and go to Validation class, but now it seems that is only regarding some client validation and it does not validate in backend middleware.
Be sure that your razor view does not add jquery.validate.min.js and jquery.validate.unobtrusive.min.js.
For using asp.net core default template,you could just avoid using the following code in your razor view:
#*#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}*#
Then add the following code to your backend:
if (!ModelState.IsValid)
{
return View(model);
}
Update:
If you want to validate before controller,I suggest that you could custom ActionFilter.Here is a working demo with asp.net core 3.1 web api:
Model:
public class Test
{
[Range(1,4)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
Custom ActionFilter:
public class ValidationFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new JsonResult(context.ModelState.Select(m=>m.Value).ToList())
{
StatusCode = 400
};
}
}
}
Controller:
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
// POST api/<controller>
[HttpPost]
public void Post(Test test)
{
}
}
Register in Startup.cs:
services.AddControllers(config =>
{
config.Filters.Add(new ValidationFilter());
});
Result:

MVC Routing in .NET Core React project isn't picking up my Controller

I am learning how to create React applications with ASP.NET Core. As a newbie I am starting at the very beginning and trying to get "Hello World" displayed on the home page. I have used Visual Studio's default React.js project template to get me started. The routes are set to default. Here are my files:
Home.js:
import React, { Component } from 'react';
export class Home extends Component {
constructor(props) {
super(props);
this.state = { message: "" };
fetch('api/Home/Message')
.then(response => response.json())
.then(data => {
this.setState({ message: data });
});
}
render () {
return (
<h1>{this.state.message}</h1>
);
}
}
HomeController.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace TestingReactDotNet.Controllers
{
[Route("api/[controller]")]
public class HomeController : Controller
{
[HttpGet]
public async Task<IActionResult> Message()
{
var response = "Hello World";
return Ok(response);
}
}
}
The problem is that the HTTP response that is being parsed to Json isn't the correct one. I have console.logged it out in order to try to debug, and it appears that response.json()) is retrieving all of the text in the default public/index.html file that comes with the template application. Does anyone have any idea why this is?
Apologies if I am missing something super obvious - I use a Mac, so the file structure and Visual Studio IDE are quite different and I have struggled to understand quite a few of the tutorials/answers already out there.
To hit your Message() function you must make a HttpGet to 'api/Home' not 'api/Home/Message'.
If you want your endpoint to be 'api/Home/Message' then you must specify the route for the Message() function like so:
// api/Home/Message
[HttpGet, Route("Message")]
public async Task<IActionResult> Message()
Class Controller is for MVC which does generate full web pages.
For Web API you need to extend ControllerBase. And you should just return your value/object straight:
[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
[HttpGet, Route("message")]
public async Task<string> Message()
{
var response = "Hello World";
return response;
}
}
I solved the problem by adding a context line in setupProxy.js
You can locate it at
ClientApp -> src -> setupProxy.js
You must add the name of controller you want to use in the array context list.

Razor Pages - Trying to Implement Action Filter on Razor Page

I want to write a custom filter which will check whether a user is logged in to my site, and redirect them back to the login page if they aren't.
I want the filter to apply automatically to the page when it loads.
I have tried the solution shown below, but the filter doesn't work at the moment.
Filter Code:
using Microsoft.AspNetCore.Mvc.Filters;
namespace MODS.Filters
{
public class AuthorisationPageFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
System.Diagnostics.Debug.Write("Filter Executed"); //write to debugger to test if working
//add real code here
base.OnActionExecuted(context);
}
}
}
Next, here's the filter attribute applied to the page model:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using MODS.Filters;
namespace MODS.Pages.Menus
{
[AuthorisationPageFilter]
public class Admin_MainMenuModel : PageModel
{
public ActionResult Admin_MainMenu()
{
System.Diagnostics.Debug.Write("Function Executed");
return new ViewResult();
}
}
}
I am under the impression that you need to call an action/method on the page for the function to apply when the page loads (please tell me if this is correct), so here is the code calling the Admin_MainMenu method in the .cshtml page file (in a code block at the top of the razor page):
Model.Admin_MainMenu();
My current thoughts are that either:
1. the filter itself is of the wrong type (could be IPageFilter instead?)
2. that the way I'm implementing it is wrong (either where I apply it to the
page model, or when I call the method on the page).
Any help is greatly appreciated. Thanks.
ActionFilterAttribute is for MVC (Controllers and Actions). For Razor Pages, you must use IPageFilter (IAsyncPageFilter for async implementation).
There are two different filter pipelines for MVC and Razor Pages
Razor Page filters IPageFilter and IAsyncPageFilter allow Razor Pages to run code before and after a Razor Page handler is run. Razor Page filters are similar to ASP.NET Core MVC action filters, except they can't be applied to individual page handler methods.
Filter methods for Razor Pages in ASP.NET Core
It's just as simple as the ActionFilterAttribute. All you need is to create an Attribute derived class that implements either IPageFilter or IAsyncPageFilter (Both would also work).
[AttributeUsage(AttributeTargets.Class)]
public class CustomPageFilterAttribute : Attribute, IAsyncPageFilter
{
// Executes first
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
// TODO: implement this
}
// Executes last
public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
// Before action execution
await next();
// After action execution
}
}
Now, you can use your attribute in your PageModel.
[CustomPageFilter]
public class IndexModel : PageModel
{
public void OnGet() { }
}
This answer is for AspNet MVC rather than AspNetCore MVC, but may be useful to someone:
If it's for Authorization, I would use the AuthorizeAttribute class.
Something like this:
using System.Web.Mvc;
namespace MODS.Filters
{
public class CustomAuthorizeUserAttribute : AuthorizeAttribute
{
// Custom property, such as Admin|User|Anon
public string AccessLevel { get; set; }
// Check to see it the user is authorized
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
System.Diagnostics.Debug.Write("Authorize Executed"); //write to debugger to test if working
// Use Core MVC Security Model
var isAuthorized = base.AuthorizeCore(httpContext);
if (!isAuthorized)
{
return false;
}
// Or use your own method of checking that the user is logged in and authorized. Returns a Boolean value.
return MySecurityHelper.CheckAccessLevel(AccessLevel);
}
// What to do when not authorized
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Error",
action = "NotFound"
})
);
}
}
}
Then Decorate the Controller or Action with the CustomAuthorizeUser Attribute:
using MODS.Filters;
namespace MODS.Pages.Menus
{
[CustomAuthorizeUser(AccessLevel = "Admin")]
public class Admin_MainMenuModel : PageModel
{
public ActionResult Admin_MainMenu()
{
System.Diagnostics.Debug.Write("Function Executed");
return new ViewResult();
}
}
}
Hope this helps!

MVC Core Controller Declaration

In the code line below below, what does the second Controller mean?
Is that a data variable declaration for HelloWorldController?
HelloWorldController : **Controller**
From MSDN Adding Controller
using Microsoft.AspNetCore.Mvc;
using System.Text.Encodings.Web;
namespace MvcMovie.Controllers
{
public class HelloWorldController : Controller
{
//
// GET: /HelloWorld/
public string Index()
{
return "This is my default action...";
}
//
// GET: /HelloWorld/Welcome/
public string Welcome()
{
return "This is the Welcome action method...";
}
}
}
It means Controller is the base type of HelloWorldController, enabling you to access all of its protected methods, and allowing it to be stored anywhere a Controller can be stored.
This relates to the inheritance part of object-oriented programming, which is a topic much too broad for a complete explanation here.

Categories

Resources