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.
Related
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);
I have a model such as
public class MyModel
{
public MyObject myObject {get;set;}
}
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
}
With out using a custom model binder everything works great. I am trying to implement a model binder and not getting anywhere -- the resources that I have come from are
https://www.youtube.com/watch?v=qDRORgoZxZU (returns null model to the controller)
http://intellitect.com/custom-model-binding-in-asp-net-core-1-0/ (controller dies on the constructor)
http://hotzblog.com/asp-net-vnext-defaultmodelbinder-and-automatic-viewmodel-string-trim/ (can not even find MutableObjectModelBinder in the .net-core namespace)
Ideally what I want is to track which properties where set by the ModelBinder.
public class MyObject
{
public string FirstName {get;set;}
public string LastName {get;set;}
public List<String> ModifiedProperties {get;set;}
}
when the object is created by the ModelBinder for each property that is being set it adds it to the ModifiedProperties list.
This is solution. You need to implement IModelBinderProvider and IModelBinder
public class EntityFrameworkModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
//We only want to invoke the CustomeBinder on IBaseEntity classes
if (context.Metadata.ContainerType != null && context.Metadata.ContainerType.GetInterfaces().Contains(typeof(SurgeOne.Core.IBaseEntity)))
{
//We only create the custom binder on value types. E.g. string, guid, etc
if (context.Metadata.ModelType.GetTypeInfo().IsValueType ||
context.Metadata.ModelType == typeof(System.String))
{
return new EntityFrameworkModelBinder();
}
}
return null;
}
}
And IModelBinder
public class EntityFrameworkModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
//Get the value
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
// no entry
return Task.CompletedTask;
}
//Set the value -- not sure what this does
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
//Set the value -- this has to match the property type.
System.ComponentModel.TypeConverter typeConverter = System.ComponentModel.TypeDescriptor.GetConverter(bindingContext.ModelType);
object propValue = typeConverter.ConvertFromString(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(propValue);
//Code to track changes.
return Task.CompletedTask;
} //BindModelAsync
}
I'm using MVC3 and currently i'm following a practice such that I declare one instance of DB Container for every controller. I use that container instance for every request coming to that controller. If I need to go to my models for a query or sth, I send that instance as a parameter to the model's function. So for the whole application, I create and use 4-5 different instances of DB Container class. My question is, does this have a good or bad effect on my database operations? Does it matter to create a seperate container instance? What is the proper way to use container classes?
I believe the mentioned class was called DBContext before.
I am not sure it is what you mean but I can give you an example of an approach I'm following rather often:
Create a sort of 'domainservice class' for the DBContext
public class MyDomainService : IDisposable
{
private MyDbEntities dbo;
private bool isDisposed;
public MyDomainService()
{
dbo = new MyDbEntities();
}
public User GetUser(string userName)
{
return (from usr in dbo.Users
where usr.UserName == userName
select usr).SingleOrDefault();
}
public void Dispose()
{
if (isDisposed)
return;
isDisposed = true;
dbo.Dispose();
}
}
Create a custom Controller class that extends Controller or AsyncController
and override the Initialize and Dispose methods:
public class MyController : Controller
{
protected MyDomainService DomainService { get; private set; }
protected override void Initialize(System.Web.Routing.RequestContext
requestContext)
{
base.Initialize(requestContext);
DomainService = new MyDomainService();
}
protected override void Dispose(bool disposing)
{
DomainService.Dispose();
base.Dispose(disposing);
}
}
Now you can use the following approach in per example the HomeController inheriting MyController
public class HomeController : MyController
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(string username)
{
var user = DomainService.GetUser(username);
if (user != null)
return RedirectToAction("Account", "Information");
return View();
}
}
This will keep your controllers rather clean.
I have written Custom model binder for my MVC3 application. I desided to use the Custom model binder is because I am using Sessions and unit tests are failing because of them.
Now my problem is that About action does not accept any parameter but it need to pass the values stored in cart to view without using Session. Because with session it will fail the unit test. Model binder only works if I pass the cart as parameter to About.
Please suggest me if you have any ideas.
many Thanks
Model Binder
public class CartModelBinder : IModelBinder
{
private const string CartSessionKey = "Cart";
public object BindModel(ControllerContext controllerContext, ModelBindingContext modelBindingContext)
{
Cart cart = null;
if(IsCartExistInSession(controllerContext))
{
cart = GetCartFromSession(controllerContext);
}
else
{
cart = new Cart();
AddCartToSession(controllerContext, cart);
}
return cart;
}
private static Cart GetCartFromSession(ControllerContext controllerContext)
{
return controllerContext.HttpContext.Session[CartSessionKey] as Cart;
}
private static void AddCartToSession(ControllerContext controllerContext, Cart cart)
{
controllerContext.HttpContext.Session[CartSessionKey] = cart;
}
private static bool IsCartExistInSession(ControllerContext controllerContext)
{
return controllerContext.HttpContext.Session[CartSessionKey] != null;
}
}
Controller
[HttpPost]
public ActionResult AddToCartfromAbout(Cart cart, int productId = 2)
{
var product = _productRepository.Products.First(p => p.ProductId == productId);
cart.AddItem(product, 1);
return View("About");
}
public ActionResult About()
{
// Need something here to get the value of cart
return View(cart);
}
This Link may solve you problem. You need to download the source and DLLs from the link above and them you can assign the value to Session in Test.
[Test]
public void AddSessionStarShouldSaveFormToSession()
{
// Arrange
TestControllerBuilder builder = new TestControllerBuilder();
StarsController controller = new StarsController();
builder.InitializeController(controller);
controller.HttpContext.Session["NewStarName"] = "alpha c";
// Act
RedirectResult result = controller.Index() as RedirectResult;
// Assert
Assert.IsTrue(result.Url.Equals("Index"));
}
I would suggest use Moq (or any other tool for mocking your data) and pass the values on the controller constructor. (Not sure, but mabe use Dependency Injection would help on this if it is not too much over-engineering)
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.