OData routing and action - asp.net-web-api

From example on other site:
In WebApiConfig.cs
EntitySetConfiguration<ContactType> contactType = builder.EntitySet<ContactType>("ContactType");
var actionY = contactType.EntityType.Action("ChangePersonStatus");
actionY.Parameter<string>("Level");
actionY.Returns<bool>();
var changePersonStatusAction = contactType.EntityType.Collection.Action("ChangePersonStatus");
changePersonStatusAction.Parameter<string>("Level");
changePersonStatusAction.Returns<bool>();
In ContactTypeController
[HttpPost]
[ODataRoute("Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus(ODataActionParameters parameters)
{
if (ModelState.IsValid)
{
var level = parameters["Level"];
// SAVE THIS TO THE DATABASE OR WHATEVER....
}
return Ok(true);
}
Now the action can be called:
For the Entity:
http://localhost:51902/odata/ContactType(5)/Default.ChangePersonStatus
For the Entity Collection:
http://localhost:51902/odata/ContactType/Default.ChangePersonStatus
I don't understand how we retrieve Id = 5 of ContactType in the action and save something to database by this ID when use ..odata/ContactType(5)/Default.ChangePersonStatus link

For the Entity Collection:
http://localhost:51902/odata/ContactType/Default.ChangePersonStatus
Your method in controller should be as follows:
[HttpPost]
[ODataRoute("ContactType/Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus(ODataActionParameters parameters)
{
...
}
For the Entity:
http://localhost:51902/odata/ContactType(5)/Default.ChangePersonStatus
Your method in controller should be as follows:
[HttpPost]
[ODataRoute("ContactType({key})/Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus(int key, ODataActionParameters parameters)
{
...
}
key will have value 5;

To reference a single entity, you need to include [FromODataUri] before the key:
[HttpPost]
[ODataRoute("ContactType({key})/Default.ChangePersonStatus")]
public IHttpActionResult ChangePersonStatus([FromODataUri] int key, ODataActionParameters parameters)
{
// Code
}
You should now be able to access your id within the method.

Related

TempData is null after redirection

I want use TempData to pass messages between Test1 and Test2 actions but when I read tempdata's key on Test2's action the value on TempData key is null. I read the documentation about TempData on MDSN this is what it say:
Represents a set of data that persists only from one request to the
next.
My code is that:
public class TestController : Controller
{
public const string TEMP_DATA_KEY = "TEST";
public IActionResult Test1()
{
TempData[TEMP_DATA_KEY] = "ciao";
return RedirectToAction(nameof(Test2));
}
public IActionResult Test2()
{
TempData.TryGetValue(TEMP_DATA_KEY, out object saluto);
return View();
}
}
What is wrong, Test1 is first request Test2 is next request why TempData is null?
Thank you regards
In your controller update the Test2 Action as noted below:
public const string TEMP_DATA_KEY = "TEST";
public IActionResult Test1()
{
TempData[TEMP_DATA_KEY] = "ciao";
return RedirectToAction(nameof(Test2));
}
public IActionResult Test2()
{
TempData.TryGetValue(TEMP_DATA_KEY, out object saluto);
return View(saluto);
}
Update your View (Test2.cshtml) as shown below.
#model string
#{
ViewData["Title"] = "Test2";
}
<h1>#Model</h1>
This wil show the value you've added in Test1 into your TempData in the h1 tag on the Test2 View.

Access TempData in ExecuteResult Asp.Net MVC Core

I wanted to save notification in TempData and shown to user. I create extension methods for this and implement a class which Extends from ActionResult. I need to access TempData in override ExecuteResult method with ActionContext.
Extension Method:
public static IActionResult WithSuccess(this ActionResult result, string message)
{
return new AlertDecoratorResult(result, "alert-success", message);
}
Extends ActionResult class.
public class AlertDecoratorResult : ActionResult
{
public ActionResult InnerResult { get; set; }
public string AlertClass { get; set; }
public string Message { get; set; }
public AlertDecoratorResult(ActionResult innerResult, string alertClass, string message)
{
InnerResult = innerResult;
AlertClass = alertClass;
Message = message;
}
public override void ExecuteResult(ActionContext context)
{
ITempDataDictionary tempData = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionary)) as ITempDataDictionary;
var alerts = tempData.GetAlert();
alerts.Add(new Alert(AlertClass, Message));
InnerResult.ExecuteResult(context);
}
}
Call extension method from controller
return RedirectToAction("Index").WithSuccess("Category Created!");
I get 'TempData ' null , How can I access 'TempData' in 'ExecuteResult' method.
I was literally trying to do the exact same thing today (have we seen the same Pluralsight course? ;-) ) and your question led me to find how to access the TempData (thanks!).
When debugging I found that my override on ExecuteResult was never called, which led me to try the new async version instead. And that worked!
What you need to do is override ExecuteResultAsync instead:
public override async Task ExecuteResultAsync(ActionContext context)
{
ITempDataDictionaryFactory factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
ITempDataDictionary tempData = factory.GetTempData(context.HttpContext);
var alerts = tempData.GetAlert();
alerts.Add(new Alert(AlertClass, Message));
await InnerResult.ExecuteResultAsync(context);
}
However, I have not fully understood why the async method is called as the controller is not async... Need to do some reading on that...
I find out the way to get the TempData. It need to get from ITempDataDictionaryFactory
var factory = context.HttpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
var tempData = factory.GetTempData(context.HttpContext);

C# Web Api Routing

Goal: I want to define REST verb names and link them to the REST routes. Next, I want to map custom rules to a defined action name:
GET / => Get()
GET /1 => Get(int id)
POST / => Post()
PUT / => Put()
DELETE /1 => Delete(int id)
GET /custom => [HttpGet][ActionName("custom")] GetCustom()
I have in OwinStartup.cs:
httpConfiguration.Routes.MapHttpRoute (
name: "ControllerWithId",
routeTemplate: "rest/{controller}/{id}",
constraints: new {
id = #"^\d+$"
},
defaults: new {
action = "Index",
id = RouteParameter.Optional
});
httpConfiguration.Routes.MapHttpRoute (
name: "ControllerWithAction",
routeTemplate: "rest/{controller}/{action}/{id}",
defaults: new {
action = "Index",
id = RouteParameter.Optional
});
I have an ApiController:
public class MyController : ApiController {
public async Task<IEnumerable<object>> Get() {}
public async Task<object> Get(int id) {}
public async Task Post([FromBody] data) {}
public async Task Put(int id, [FromBody] data) {}
public async Task Delete(int id) {}
[HttpGet][ActionName("custom")]
public async Task Custom() {}
}
They all return 404 except for custom. It would suck to have to add an ActionName to each REST method. What is the most concise solution?
ASP.NET MVC doesn't provide an easy method using their named conventions for CRUD/custom endpoints. I went the ugly route and explicitly specified ActionName("") for each CRUD method.
reyan-chougle's solution requires you to hardcode strings in your code which is bad practice. I tried to add magic strings to the attributes but got compiler errors.
Solution:
[HttpGet]
[ActionName (Consts.defaultActionMethdName)]
public IEnumerable<Model> Get () {}
[HttpGet]
[ActionName (Consts.defaultActionMethdName)]
public Model Get (int id) {}
[ActionName (Consts.defaultActionMethdName)]
public IHttpActionResult Post ([FromBody] Model input) {}
[ActionName (Consts.defaultActionMethdName)]
public IHttpActionResult Put (int id, [FromBody] Model input) {}
[ActionName (Consts.defaultActionMethdName)]
public IHttpActionResult Delete (int id) {}
[HttpGet]
[ActionName("custom")]
public async Task Custom() {}

filterContext.Controller.ViewData.Model is null in ActionFilter.OnActionExecuted

I have an ASP.NET MVC 3 project with a POST action that model binds to a viewmodel
[HttpPost]
public virtual ActionResult Reply(ReplyViewModel viewModel)
{
// ...
}
I have an ActionFilter and I want to look at that viewmodel in OnActionExecuted but ViewData.Model is null. Am I misunderstanding what ViewData.Model is?
public class CopyViewModelToTempDataAttribute : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuted(System.Web.Mvc.ActionExecutedContext filterContext)
{
// filterContext.Controller.ViewData.Model is null
}
}
This might be a Solution to this problem
1. I assume that you assigned Model in Action
now come to Filter
public override void OnActionExecuted(System.Web.Mvc.ActionExecutedContext filterContext)
{
if(filterContext.HttpContext.Request.HttpMethod == "POST")
{
// Get your model here.
}
}
Your controller action must pass a view model when returning the view:
[HttpPost]
public virtual ActionResult Reply(ReplyViewModel viewModel)
{
// ...
return View(someViewModel);
}
Now you will be able to fetch the returned view model in the action filter.

Bind route parameter to model

Lets say i have a route like the following
/{controller}/{action}/{id}
Is it possible to bind the id to a property in my model
public ActionResult Update(Model model)
{
model.Details.Id <-- Should contain the value from the route...
}
Where my model class is the following?
public class Model
{
public Details Details {get;set;}
}
public class Details
{
public int Id {get;set;}
}
You'll want to create your own custom model binder.
public class SomeModelBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
ValueProviderResult value = bindingContext.ValueProvider.GetValue("id");
SomeModel model = new SomeModel() { Details = new Details() };
model.Details.Id = int.Parse(value.AttemptedValue);
//Or you can load the information from the database based on the Id, whatever you want.
return model;
}
}
To register your binder you add this to your Application_Start()
ModelBinders.Binders.Add(typeof(SomeModel), new SomeModelBinder());
Your controller then looks exactly as you have it above. This is a very simplistic example but the easiest way to do it. I'll be happy to provide any additional help.

Resources