How to populate ViewDataDictionary inside an ExceptionFilter - asp.net

How to retrieve the ViewModel from within an exception filter?
I have an ExceptionFilter, which I am using for a global error handler in an asp .net core 3.1 MVC application. I am trying to get the exception filter to redirect back to the View when there is an error and show validation errors, ie the equivalent of saying:
return View(viewModel)
in the controller
I can redirect to the View, but am a little stuck on how to populate the Model in the ViewResult
ExceptionFilter code
public void OnException(ExceptionContext context)
{
string controller = context.RouteData.Values["controller"].ToString();
string action = context.RouteData.Values["action"].ToString();
if (context.Exception is WebServiceException && context.Exception.IsUnauthorized())
{
context.Result = new RedirectToActionResult("fetchtoken", "Home", new { path = $"/{controller}/{action}" });
}
//other type of exception, return the view displaying errors
else
{
context.ModelState.Clear();
context.ModelState.AddModelError(action, $"error in {action}");
m_Logger.LogError(context.Exception, $"error in {action}");
context.ExceptionHandled = true;
context.ModelState
context.Result = new ViewResult{
ViewName = action,
ViewData = // ??????????????
};
}
}
In the controller:
[Authorize]
[HttpPost]
public async Task<IActionResult> AuthoriseApiUser(AuthoriseApiViewModel viewModel)
{
await m_ApiUserService.AuthoriseUser(viewModel.TenantId, viewModel.UserId); //error thrown here
return View(viewModel);
}

Through obtaining the value of each key in the form data, the value is compared with the property of the model. Then, assign value to model. For example.
public void OnException(ExceptionContext context)
{
string controller = context.RouteData.Values["controller"].ToString();
string action = context.RouteData.Values["action"].ToString();
//start
var viewModel = new ViewModel();
var list = context.HttpContext.Request.Form.AsEnumerable();
foreach (var meta in list)
{
if (meta.Key == "addr")
{
viewModel.addr = meta.Value;
}
}
//end
if (context.Exception is WebServiceException && context.Exception.IsUnauthorized())
{
context.Result = new RedirectToActionResult("fetchtoken", "Home", new { path = $"/{controller}/{action}" });
}
//other type of exception, return the view displaying errors
else
{
//...
var modelMetadata = new EmptyModelMetadataProvider();
context.Result = new ViewResult
{
ViewName = action,
ViewData = ViewData = new ViewDataDictionary(modelMetadata, context.ModelState)
{
Model = viewModel
}
};
}
}
Model
public class ViewModel
{
public int id { get; set; }
[MinLength(2)]
public string addr { get; set; }
}

Related

Call a rest web api in asp.net and receive error

Hi I want to call a rest Web Api and I use asp.net MVC+Web Api.
I write a get Token Method like below :
public TokenViewModel GetToken()
{
//string Result = string.Empty;
TokenViewModel token = null;
string baseAddress = "http://$$$$$$$$$$/api/security/login";
using (HttpClient client = new HttpClient())
{
try
{
var url = new Uri(baseAddress);
MultipartFormDataContent form = new MultipartFormDataContent();
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters.Add("UserName", "###");
parameters.Add("Password", "$$$");
HttpContent DictionaryItems = new FormUrlEncodedContent(parameters);
form.Add(DictionaryItems, "model");
var response = client.PostAsync(url.ToString(), form, System.Threading.CancellationToken.None);
if (response.Result.StatusCode == System.Net.HttpStatusCode.OK)
{
//Get body
var bodyRes = response.Result.Content.ReadAsStringAsync().Result;
token = JsonConvert.DeserializeObject<TokenViewModel>(bodyRes);
//Get Header
// var headers = response.Result.Headers.GetValues("appToken");
}
else
{
var a = response.Result.Content.ReadAsStringAsync().Result;
}
}
catch (Exception ex)
{
}
return token;
}
}
And also webController:
namespace WebAPI.Controllers
{
public class WebApiController : ApiController
{
private readonly GetToken_BLL _tokenService;
public WebApiController(GetToken_BLL tokenService)
{
_tokenService = tokenService;
}
public object Verfiybll { get; private set; }
public class stcAPIMessage
{
public string Message { get; set; }
public HttpStatusCode StatusCode { get; set; }
}
[HttpPost]
[Route("api/Token")]
public IHttpActionResult Token()
{
stcAPIMessage message = new stcAPIMessage();
GetToken_BLL tokenbll = new GetToken_BLL();
var result = tokenbll.GetToken();
if (result == null)
{
message.Message = "error in recieveing token";
message.StatusCode = HttpStatusCode.BadRequest;
return Content(message.StatusCode, message.Message);
}
else if (string.IsNullOrEmpty(result.Token))
{
message.Message = "Error";
message.StatusCode = HttpStatusCode.BadRequest;
return Content(message.StatusCode, message.Message);
}
return Ok(result);
}
}
}
When I run the program it throw out error:
An error occurred when trying to create a controller of type 'Web ApiController'.
Make sure that the controller has a parameter less public constructor.
System. Invalid Operation Exception Type 'WebAPI.Controllers.
Web ApiController' does not have a default constructor
System.
The parameter less constructor error is common in ASP.NET web applications that use dependency injection.
I have noticed there is a constructor parameter being used:
GetToken_BLL _tokenService
Use a dependency injection resolver for the type GetToken_BLL so that the parameter _tokenService can be instantiated.

Not use URL redirect with ForbidResult or Pass Data to Acccount AccessDenied View from ASP.NET Core and Identity

'ForbidResult' below is used, which causes a url redirect. Thus, Context.Items["data"] is lost for the redirected page, which is /MicrosoftIdentity/Account/AccessDenied?ReturnUrl=SomeSite.
public class PermissionAttribute : TypeFilterAttribute
{
private class PermissionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
context.HttpContext.Items["data"] = "some data";
var authorized = check_if_it_has_permissions();
if (authorized)
{
await next();
}
else
{
context.Result = new ForbidResult(); //this is a url redirect using /MicrosoftIdentity/Account/AccessDenied?ReturnUrl=SomeSite
}
}
}
}
partial_view_header.cshtml
<div id="header">
#{
Context.Items["data"]
}
</div>
Is it possible to keep the current url, and not change the url to '/MicrosoftIdentity/Account/AccessDenied?ReturnUrl=xxx', or a way to pass data to Acccount AccessDenied view?
You can try to use Session,or you can pass data with querystring,here is a demo with session:
PermissionFilter:
public class PermissionFilter : Attribute,IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
context.HttpContext.Session.SetString("data", "some data");
context.Result = new RedirectToRouteResult
(
new RouteValueDictionary(new
{
action = "Account",
controller = "Home",
//data = "some data"
}));
return;
}
}
HomeController:
[PermissionFilter]
public IActionResult Index()
{
return View();
}
public IActionResult Account() {
TempData["data"] = HttpContext.Session.GetString("data");
return View();
}

ASP.NET Core custom binding ModelName always empty

I have implemented custom binder:
public class CustomModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelName = bindingContext.ModelName;
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
var modelAsString = valueProviderResult.FirstValue;
if (!string.IsNullOrEmpty(modelAsString))
{
// custom login here
}
return Task.CompletedTask;
}
}
and corresponding model binder provider:
public class CustomModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(CustomModel))
{
return new BinderTypeModelBinder(typeof(CustomModelBinder));
}
return null;
}
}
and I have added custom binder provider to ModelBinderProviders collection:
services.AddMvc(options => options.ModelBinderProviders.Insert(0, new CustomModelBinderProvider()));
Everything works except one thing. Custom binder is being called but unfortunatelly bindingContext.ModelName is always empty and don't know why. As ModelName is empty I always get ValueProviderResult.None as value provider.
var modelName = bindingContext.ModelName; //ModelName is empty here
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); // I will get ValueProviderResult.None on this line
I don't know what am I missing in terms of ModelName and ValueProvider.
UPDATE:
CustomModel is used in controller which inherites ApiController. Method signature in controller looks like this:
[HttpPost]
public async Task<IActionResult> UpsertModel(string Id, [FromBody] CustomModel model)

An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter

I am struck at this code, i get this error when whenever i tried to see a list of articles.
the error is The parameters dictionary contains a null entry for parameter articleId of non-nullable type System.Int32 for method System.Web.Mvc.ActionResult Detail(Int32) in Crossroads.Controllers.ArticleController. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters.
Here is the controller:
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Security;
using Crossroads.Models;
using System.Linq;
using System.Web;
using Crossroads.Models.Repositories;
namespace Crossroads.Controllers
{
[HandleError]
public class ArticleController : BaseController
{
private IArticleRepository _ArticleRepository;
public ArticleController()
: this(new ArticleRepository())
{ }
public ArticleController(IArticleRepository articleRepo)
{
_ArticleRepository = articleRepo;
}
public ActionResult Index(bool? archived)
{
if (archived != null && archived.Value.Equals(true))
{
return View("IndexArchived", _ArticleRepository.ListArchivedArticles());
}
else
{
return View(_ArticleRepository.ListArticles());
}
}
public ActionResult Detail(int Id)
{
var article = _ArticleRepository.GetArticle(Id);
if (article.Published)
{
return View("Details", article);
}
else
{
postStatus = new PostStatusViewModel()
{
Status = Status.Warning,
Message = "I'm sorry, article " + article.Title + " has been deleted. <br />Please update your bookmark."
};
return RedirectToAction("PostStatus", postStatus);
}
}
public ActionResult Details(int id, string articleTitle)
{
return RedirectToAction("Detail", id);
}
[Authorize(Roles = "Manager")]
public ActionResult New()
{
return View("New", new Articles());
}
[Authorize(Roles = "Manager")]
[ValidateInput(false)]
public ActionResult Edit(int id)
{
var viewModel = _ArticleRepository.GetArticle(id);
return View(_ArticleRepository.GetArticle(id));
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
[Authorize(Roles = "Manager")]
public ActionResult Create(Articles model)
{
try
{
model.CreatedBy = Membership.GetUser().UserName;
model.CreatedDate = DateTime.Now;
model.ModifiedBy = Membership.GetUser().UserName;
model.ModifiedDate = DateTime.Now;
_ArticleRepository.CreateArticle(model);
return RedirectToAction("Index");
}
catch
{
return View("Edit", model.Id);
}
}
[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
[ValidateInput(false)]
[Authorize(Roles = "Manager")]
public ActionResult Update(Articles model)
{
try
{
model.ModifiedBy = Membership.GetUser().UserName;
model.ModifiedDate = DateTime.Now;
_ArticleRepository.EditArticle(model);
return RedirectToAction("Details", new { id = model.Id });
}
catch (Exception ex)
{
_DBLogging.LoggError(ex);
postStatus = new PostStatusViewModel()
{
Status = Status.Error,
Message = ""
};
return View("Edit");
}
}
public JsonResult ManageArchiveArticle(int articleId, bool isArchived)
{
var result = new Dictionary<string, string>();
try
{
var article = _ArticleRepository.GetArticle(articleId);
article.IsArchived = isArchived ? false : true;
article.ModifiedBy = Membership.GetUser().UserName;
article.ModifiedDate = DateTime.Now;
if (article.IsArchived) article.ArchiveDate = DateTime.Now;
article = _ArticleRepository.EditArticle(article);
result.Add("isArchived", article.IsArchived.ToString().ToLower());
}
catch (Exception ex)
{
_DBLogging.LoggError(ex);
}
return Json(result);
}
public JsonResult ManagePublishArticle(int articleId, bool isPublished)
{
var result = new Dictionary<string, string>();
try
{
var article = _ArticleRepository.GetArticle(articleId);
article.Published = isPublished ? false : true;
article.ModifiedBy = Membership.GetUser().UserName;
article.ModifiedDate = DateTime.Now;
article = _ArticleRepository.EditArticle(article);
result.Add("isPublished", article.Published.ToString().ToLower());
}
catch (Exception ex)
{
_DBLogging.LoggError(ex);
}
return Json(result);
}
}
}
here is my index
<ul class="menu">
<li><%= Html.ActionLink("Article List", "Index", "Article")%></li>
<li><%= Html.ActionLink("New Article", "New", "Article")%></li>
</ul>

Returning view from within an Area, results in different view being displayed

When I run the website, I'm expecting the Home view to be displayed but its not finding the view. If i alter the code below, and remove the .mvc from ("{controller}.mvc/{action}" I get the base index view, not the Home view i wish to get from the Home Area.
What I noticed after I removed .mvc from the Route - The Home controller located inside Areas-Home-Controllers-HomeController is being found and executed, when the view gets returned and the AreaViewEngine takes ahold of it, it doesn't see the Areas so it returns base index view... Any clue what could be causing this!?
I have the following RegisterRoutes
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapAreas("{controller}/{action}/{id}",
"ProjectNamespace.Web",
new[] { "Home" });
routes.MapRootArea("{controller}.mvc/{action}",
"ProjectNamespace.Web",
new { controller = "Home", action = "Index" });
}
Here is ViewFind located in AreaViewEngine.cs:
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
ViewEngineResult areaResult = null;
if (controllerContext.RouteData.Values.ContainsKey("area"))
{
string areaViewName = FormatViewName(controllerContext, viewName);
areaResult = base.FindView(controllerContext, areaViewName, masterName, useCache);
if (areaResult != null && areaResult.View != null)
{
return areaResult;
}
string sharedAreaViewName = FormatSharedViewName(controllerContext, viewName);
areaResult = base.FindView(controllerContext, sharedAreaViewName, masterName, useCache);
if (areaResult != null && areaResult.View != null)
{
return areaResult;
}
}
return base.FindView(controllerContext, viewName, masterName, useCache);
}
The BaseController which is located in the default Controllers root directory:
public abstract class BaseController : Controller
{
public virtual ActionResult Index()
{
return View();
}
}
Here is the Home controller located in Areas-Home-Controllers:
public class HomeController : BaseController
{
public HomeController()
{
}
public override ActionResult Index()
{
HomeDetailsViewModel model = new HomeDetailsViewModel();
model.User = "testing username"
return View("Index", model);
}
}
Finally, screenshot of the Solutions Explorer:
for an area you have to specify it. try this
return RedirectToAction("Index", "Home", new { area = "Home" });

Resources