Category: ASP.NET MVC

MVC Browser history provider for azure–trying an implementation for 3 hours

first, implement  IBrowserUserHistoryRepository  – that means implement:

public void Save(IEnumerable<BrowserUserHistoryData> history)

 

Azure have PartitionKey/RowKey – I have to add a new class.

Also or connectiing, I have to put

 

connectionString="UseDevelopmentStorage=true;" /

 

I tried to add a bulk history :
tableHistory.ExecuteBatch(batchOperation);
The result was:
Unexpected response code for operation : 0
Magic: 
<add key="TableStorageEndpoint" value="http://127.0.0.1:1002/"/>
And one hour has been gone.
Run dsinit to have storage emulator:
No connection could be made because the target machine actively refused it 127.0.0.1:10002

modified code to old Azure code:

now the answer was:

One of the request inputs is out of range.

http://msdn.microsoft.com/en-us/library/dd135715.aspx – All letters in a container name must be lowercase.

Tried that – same result:

One of the request inputs is out of range.

Maybe timestam is wrong? No…

Now debug with Fiddler :

http://sepialabs.com/blog/2012/02/17/profiling-azure-storage-with-fiddler/

image

 

Added to connection string:

 

DevelopmentStorageProxyUri=http://ipv4.fiddler
 And see this in Fiddler :

<d:PartitionKey>zungb4ovunqjd5rtal5ytc3r</d:PartitionKey>

<d:RowKey>http://localhost:2728/</d:RowKey>

: base(UserName, UserName)
 
So the problem is that RowKey does not support url values.
Now , after removing url from the RowKey  - and put username, the error was:

The specified entity already exists

Another hour passes

——————

Now, that it works, thinking about rowkey and partitionkey : no username + url => put date.ToString("yyyyMMdd_HHmmss_tttt")

0:The specified entity already exists.

Oh no, not again?

Look tables =>20121220_064024_AM -  ok, it should be

date.ToString("yyyyMMdd_HHmmss_ffffzzz")

0:The specified entity already exists

Again? debug, please

The real problem:

Forget about sending whole items history - not just not saved ones…
Now it works – kind of

Server Error in ‘/’ Application.


The method or operation is not implemented.

public IEnumerable<KeyValuePair<string, int>> MostUsed(int Count, DateTime? date)
Line 80:         {
Line 81:             throw new NotImplementedException();
Line 82:         }
Line 83: 
-----------
Implementing MostUsed(int Count, DateTime? date) 
Research about filter with data - http://storageextensions.codeplex.com/SourceControl/changeset/view/81826#1914483
Research about GroupBY – not supported!http://msdn.microsoft.com/en-us/library/windowsazure/dd135725.aspx
So now thinking about a way to STORE the data in a convenient format to can retrieve…
 It must take into consideration Count for a date and Count for all dates( date can be null) – AND BOTH THE FACT THAT THE OPERATION WILL BE DONE PER USER.
Time to think – because another hour has passed!
 

Browser history–part 5–conclusions

This is the part 5 of 5 of my implementing of a MVC Browser history

MVC browser history – idea

Browser history –2 – implementing, small bugs

Browser history 3–trying to Nuget – modifications in order to can be transformed from an application to a component

Browser history 4–NuGet again -  finally Nuget deployment

Browser history–part 5–conclusions  – conclusions

 

 

I have reached the (partial) final for my MVC Browser History .  It has started as a simple application – and now it is a NuGet component ready to be used and a demo project at app harbour

What can do for you as a MVC programmer :

  • The component can be integrated in any MVC application easily (via NuGet)
  • you will have a page that displays the list of links that are part of the project and the top 5(configurable) accessed pages
  • You can have this filtered per user basis – or per administrator( small changes in the controller action)
  • It can save the history in memory or in sql server database( configurable in History page, ) or you can make your saving data by implementing  IBrowserUserHistoryRepository
  •  

    You can see in action at app harbour

    If you use, please send me an email.

    If you worry about code source, you can find at github . If you want to improve, feel free to participate Winking smile

    Browser history 4–NuGet again

    This is the part 4 of 5 of my implementing of a MVC Browser history

    MVC browser history – idea

    Browser history –2 – implementing, small bugs

    Browser history 3–trying to Nuget – modifications in order to can be transformed from an application to a component

    Browser history 4–NuGet again – finally Nuget deployment

    Browser history–part 5–conclusions – conclusions

    Prior to Nuget, I think that will be better if the user of my app could play with sql server or memory implementation. So the new GUI is here:

    image

     

    But how could I show to the user where it is saved? Easy – to the history page

    image

     

    And now I can make the NuGet Package .

    In order to can download a controller and not overwrite the Home controller, I must create a controller of its own – so make a modification : History Controller

    Now adding dependencies, fighting with NuGet Explorer,

    In the first test  StructureMap dll reference missing and the Views folder was in the wrong place.

    In the second test , I discovered that I was missing adding the following essential lines

    filters.Add(new BrowserHistory.Models.BrowserUserHistoryFilter());
    ObjectFactory.Configure(ce => ce.For<IBrowserUserHistoryRepository>().Use<BrowserUserHistoryRepositoryMemory>());

     

    So I began reading how to add to global.asax at nuget package installing time – and coming with this:http://blogs.msdn.com/b/davidebb/archive/2010/10/11/light-up-your-nupacks-with-startup-code-and-webactivator.aspx

    Adding code,testing -  another 15 minutes.

    Adding readme.txt – in order to explain what have I done and how to use it – another 30 minutes.

    Now it is ready to be used by you.

    If you want to test it, you can see in action at http://browserhistory.apphb.com/
    Detailed history of creating project at http://msprogrammer.serviciipeweb.ro/category/browserhistory/
    Source code at https://github.com/ignatandrei/MVCbrowserHistory

     

    Next time will do a summary of what I have done in order to build this simple utility project.

    Browser history 3–trying to Nuget

    This is the part 3 of 5 of my implementing of a MVC Browser history

    MVC browser history – idea

    Browser history –2 – implementing, small bugs

    Browser history 3–trying to Nuget – modifications in order to can be transformed from an application to a component

    Browser history 4–NuGet again – finally Nuget deployment

    Browser history–part 5–conclusions – conclusions

    TL;DR : Trying to add a feature (NuGET ) will conduce you to other features – apparently easy to implement – but took 2 hours…

     

    Content:

     

    First, source control is more important – http://www.joelonsoftware.com/articles/fog0000000043.html .I choose github  – deploy is a breeze with GitHub for windows . More, appharbor integration with github is awesome – you can see the application live at http://browserhistory.apphb.com/ .

     

    Now , for generating Nuget package, I should be moving the classes on their own assembly / dll. Also , added this blog to the info. Ensure it works the same. Uploaded to GitHub

    image

    And appharbor just deployed

    image

     

    Now, it will be good if I let user switch implementation between memory saving  and database saving… in order to developers( that have installed the dll with  NuGet  ) switch easily to their database.

    Created

    BrowserUserHistoryRepositorySqlServer
    

    and , after some implementing of IBrowserUserHistoryRepository , the method

    public IBrowserUserHistoryRepository FilterByUser(string UserName)

    put some problems – but solved with a private variable.

     

    Now must decide the best way to switch between MemoryRepository and SqlServerRepository in

    public static T AddOrRetrieveFromApplication<T>(HttpApplicationStateBase app)
    where T:new()
    

    First, we must retrieve at runtime the instance of the interface – so structuremap to the rescure.

    Second, if we need to retrieve a class – must have at least a default constructor.

    So the constraint of the T was gone away   – and the code is

    T result;

    if (typeof(T).IsInterface) {

    result =(T) ObjectFactory.GetInstance(type) ;

    }

    else

    {

    result = (T)Activator.CreateInstance(type);

    }

    Also, in global.asax the following lines were added:

    //ObjectFactory.Configure(ce => ce.For<IBrowserUserHistoryRepository>().Use<BrowserUserHistoryRepositoryMemory>());             //uncomment those for sql server ce             ObjectFactory.Configure(ce => ce.For<IBrowserUserHistoryRepository>().Use<BrowserUserHistoryRepositorySqlServer>());

    Now, when deploy , AppHarbor will NOT found EntityFramework dll.

    Added HintPath to csproj to find EF

    <HintPath>..\packages\EntityFramework.5.0.0\lib\net40\EntityFramework.dll</HintPath>
    as seen at
    http://blog.appharbor.com/2012/04/24/automatic-migrations-with-entity-framework-4-3

    and it works!

    More, it works with sql server AppHarbor addon  and transformation of web.config.

    image

     
    It took 2 hours to do this thing, apparently simple.
    image
    Now you can find the code source on github at https://github.com/ignatandrei/MVCbrowserHistory
    You can browse the application at http://browserhistory.apphb.com/

    Next time: (maybe some tests and ) NuGET!

    Browser history -2

    This is the part 2 of 5 of my implementing of a MVC Browser history

    MVC browser history – idea

    Browser history –2 – implementing, small bugs

    Browser history 3–trying to Nuget – modifications in order to can be transformed from an application to a component

    Browser history 4–NuGet again – finally Nuget deployment

    Browser history–part 5–conclusions – conclusions

    Now we repair the important issues and make some GUI adjustements to be nicer to the user GUI.

    1. Why the history does not display?
      • Because OnResultExecuted  is executed AFTER the View that display the results  is executed
      • Fix: do not display the ones that have no title. Modified ViewModel to take an IEnumerable.
    2. Why the count does not display after 2 clicks?
      • Because it was not saved in BrowserUserHistoryRepositoryMemory – only after SaveToRepositoryInterval links number
      • Fix: In the action save again to have latest clicks. Also modified interface Save to take an IEnumerable – and not a class.
    3. Modified GUI interface to have links where the URL displays – add ViewUrl shared VIew in MVC.
    4. Add numbering to the table ( 1,2,…)
    5. Add texts ( instead of My MVC application – My Browser History application)
    6. Add Filter By User – to be easier for the programmers that will use the program.
    7. Maybe will be useful to add sorting and filtering   – but only if someone asks.

    The code is here

    MVC browser history

    This is the part 5 of 5 of my implementing of a MVC Browser history

    MVC browser history – idea

    Browser history –2 – implementing, small bugs

    Browser history 3–trying to Nuget – modifications in order to can be transformed from an application to a component

    Browser history 4–NuGet again – finally Nuget deployment

    Browser history–part 5–conclusions – conclusions

    I want to do another small open source utilities project – MVC browser history . That seems simple at the beginning – but , yet , there are small tasks to do that are not so easy .

    What does MVC browser history  does? Well, keep an history of the links the user clicks through an MVC site. I must eliminate the ajax requests and Child requests – so keep the just the URL’s

    So what I have learned after one hour:

    Thinking process :

    I must somehow maintain the url’s – either in database , either in memory – so a Repository concept came handy.

    Data to be saved includes :

    1. URL –easy –  HttpContext.Request.Url.ToString();
    2. Date – easy – DateTime.Now;
    3. UserName – not so easy – what if user not registered?
    4. PageName – it should belong to that, but, from a RelationalDatabase in a 3rd form, should be another table.

    Named this class BrowserUserHistoryData ( rather a long name, but descriptive)

    Now detailing:
    URL – possible problem : differentiate between Person/Edit/1 and Person/Edit/2. It’s a feature, not a bug Winking smile : browser history, not Action history. More, the User can have acces to Person/Edit/1 and NOT Person/Edit/2.
    UserName – maybe Session , if not registered? (con.User.Identity.IsAuthenticated ? con.User.Identity.Name : con.Session.SessionID); , if not WebApp, then … ok, not for this time.

    How to obtain the data? The global filter from http://bradwilson.typepad.com/blog/2010/07/aspnet-mvc-filters-and-statefulness.html

    What to obtain ? A user history and first 5 links order by count

    Programming:

    Errors repaired:

    1. Brad Wilson put data in context.HttpContext.Items . Forget that is good for one request only. Thinking about how to persist and obtain data in MVC action and Global filter- and storing to Application . Made public static T AddOrRetrieveFromApplication
    2. Adding ViewName in OnResultExecuting, not in OnResultExecuted.
    3. Reading data from Repository WITHOUT persisting there Winking smile.
    4. Thinking that the Model of the page is just the list of user history. Nope, it is also the first 5 links order by count : HistoryViewModel
      public class HistoryViewModel
          {
              public BrowserUserHistory UserHis;
              public IBrowserUserHistoryRepository rep;
          }
      

    Errors not repaired:

    1. Not showing just HIS user browser history, but all ( not to be repaired, since Cassini changes session_id all the time) . NOT TO BE REPAIRED.
    2. Latest links, history himself, not show view name

    image

    What I have enjoyed:

    1. Programming process
    2. The idea that will be useful to other people
    3. Linq:  – can be translated to database also.
       public IEnumerable<KeyValuePair<string,int>> MostUsed(int Count, DateTime? date)
              {
                  var data= historyMemory.Where(item =>(date==null || item.Date.Subtract(date.Value).Days == 0)).GroupBy(item=>item.Url).OrderByDescending(g=>g.Count()).Take(Count);
                  return data.Select(i =>new KeyValuePair<string,int>( i.Key,i.Count()));
              }
      

    What to do next:

    1. Correct the error
    2. Test more the application with a real application
    3. Make the application understandable – comments.
    4. Make the application publicly available
    5. Spread the word ( already did, but not enough).

    Code can be downloaded from BrowserHistory

    . Play with it – and, if you like, drop a comment.

    String Truncate in MVC

    I always wanted a string truncate  – i.e.,  if you have a long string, to have “show less” and “show more”.

    So I have decided to do it .

    1. See in action first:

    image

    And after click:

    image

    2. How can you use in your MVC projects:

    Add in your project the \Views\Shared\DisplayTemplates\StringTruncate.cshtml file ( in this folder!) and put
    @Html.DisplayFor(m => m.String1, “StringTruncate”)

    3. Code: It implies only a div and a hidden – and javascript, of course.

    @model string
    
    @{
        string id = ViewData.ModelMetadata.PropertyName;
       string textFull = Html.Encode(Model??"");
       string PartialText = textFull;
       string less = "";
       bool full = true;
       if (PartialText.Length > 20)
       {
           full = false;
           PartialText = PartialText.Substring(0, 20) + "...<a href=javascript:showHide_" + id + "(true)>(more)</a>";
       }
    
       }
    
    <div title="@textFull" id='@("display"+ id)'>
            @Html.Raw(PartialText )
    
        </div>
        @{if (full) {
              return;
          }
         }
    
    <div  id='@("hid" + id)' style="display:none">
    @textFull
    <p><a href='javascript:@("showHide_"+ id) (false)'>less</a></p>
    </div>
    <script type="text/javascript">
    function @("showHide_"+ id) (showFull){
    
    $("#@("display"+ id)").toggle(!showFull);
    
    $("#@("hid"+ id)").toggle(showFull);
    
    }
    </script>
    
    

    4. Source code here:

    string Truncate

    5. TODO ( homework):

    Modify the source in order to have one javascript and send the ids of the hidden and of the div to the function.

    Clearer as Nuget package

    It was not so difficult to make Clearer as NuGet package. Just analyzing MVC Sitemap package done the trick. So the package looks like:

    image

     

    You can install from http://nuget.org/packages/Clearer or download a sample application  http://clearer.codeplex.com

    ( What he does ?

    In every application there are some variables that are set by the ASP.NET application( ASP.NET_SessionId cookie ) and some that are set by the programmer( cached data in Application/Session/Cache/Cookies and so on).
    I wanted every time to have a page where I can “clear” / delete those – and not found. So it’s the Clearer project.
    Possible uses:
    For developers – when they want to see what happens when a cache item no longer exists
    For developers – to put to site admins some simple tool to reload data from Cache/Application . Just edit the LoadAll function to load only Cache/Application
    For developers – to test easily the session. Just delete ASP.NET_SessionId cookie – you will get another one when you refresh the page.

    )

    So please install Nuget package from http://nuget.org/packages/Clearer and tell me if it works!

    Clearer MVC

    In every application there are some variables that are set by the ASP.NET  application(  ASP.NET_SessionId  cookie ) and some that are set by the programmer( cached data in Application/Session/Cache/Cookies and so on).

    I wanted every time to have a page where I can “clear” / delete those – and not found. So it’s the Clearer project.

    It consists of :

    1. ClearerController with 2 Actions:   Index and DeleteItem
    2. 2 Views : Index.cshtml and EditAppData.cshtml
    3. Different Models:
      • SourceData  – enum  – can be  :  None ,        Application ,        Cache ,        Session ,        Cookies
      • AppData –  maintains Key/Value and SourceData pairs
      • ListAppData – loads data from Application , Cache , Session , Cookies  – and deletes.

    To make an example, I have put in the Application_Start and Session_Start different values. So the screen is the following:

    image

    What I learn from the code:

    1. The Cookies, Applications, Session , Cache items can be easily converted to an DictionaryEntry and the code can be like this:
       DictionaryEntry de = new DictionaryEntry(item, sess[item.ToString()]);
       AddNew(de, SourceData.Session);
      
    2. Code must be error prone – what if some item in Session is null ? So , if I have the Key, all is good:
      private void AddNew(DictionaryEntry de, SourceData sd)
              {
                  AppData ap = new AppData() { source = sd, Key = de.Key.ToString() };
                  try
                  {
                      var obj = de.Value;
                      ap.Value = (obj == null) ? Null : obj.ToString();
                  }
                  catch (Exception ex)
                  {
      
                      ap.Value = string.Format(ErrorToString, ex.Message);
                  }
                  this.Add(ap);
              }
      
    3. The dog-food is good: I have followed my advice from msprogrammer.serviciipeweb… and it works ( used for Remove )
         [HttpPost]
              public JsonResult DeleteItem(string TheKey, int Source)
              {
                  try
                  {
                      var lad = new ListAppData();
                      lad.DeleteItem(TheKey, (SourceData)Source);
                      return Json(new { ok = true, message = "" });
                  }
                  catch (Exception ex)
                  {
                      return Json(new { ok = false, message = ex.Message });
                  }
                  
              }
      
    4. When you pass strings in Javascript, there is a simple way to encode: HttpUtility.JavaScriptStringEncode
      <a href="javascript:removeItem('@HttpUtility.JavaScriptStringEncode(Model.Key)','@((int)Model.source)','@id')">Remove</a>
      

    Possible uses:

    1. For developers –  when they want to see what happens when a cache item no longer exists
    2. For developers – to put to site admins some simple tool to reload data from Cache/Application . Just edit the LoadAll function to load only Cache/Application Winking smile
    3. For developers  – to test easily the session. Just delete ASP.NET_SessionId  cookie – you will get another one when you refresh the page.

    You can view online at http://clearer.apphb.com/Clearer
    The project could be found at http://clearer.codeplex.com and have all – source code, downloadable project .

    Next week it will be a Nuget item.

    For more features , please leave me a comment here or on codeplex at issues
    Nuget package at http://nuget.org/packages/Clearer

    Usual Methods to transfer data from Page To Page in ASP.NET MVC

    Preamble:

    In ASP.NET ( like in PHP and other Web frameworks) there are 2 clear entities: Server ( code on the WebServer ) and Client( the HTML interpreted by the browser and javascript).

    Server and Client shares same cookies – means Client and Client both can read and write cookies.

    Transfer from the Client to Server happens when

    a) you click a link : the information to transfer is query string . That means, http://…/a?x=y&a=b will send information y ( associated to key x) and b( associated to key a). This is called a GET

    b) you press a submit button to send a FORM : the information is values of select and input. This is called a POST.

    c) you send information via javascript ( including AJAX) . Usually this can involve a PUT, a GET, or other ( see REST ).

    d) Creating/Modifying and send cookies. The sending happens automatically by the browser .

    Transfer from the Server to Client

    a)sending text(HTML)/binary data. . The interpretation is done by the browser( how to display html, how to display send file …)

    b) Creating/Modifying and send cookies . Browser will do automatically this.

    ASP.NET WebForms way:

    For ASP.NET Webforms the modalities to transfer are detailed by Peter Bromberg , http://www.eggheadcafe.com/tutorials/asp-net/e653f028-01fb-4d0e-843b-058deae562a2/eight-different-ways-to-transfer-data-from-one-page-to-another-page.aspx .

    ASP.NET MVC way:

    I want to discuss from ASP.NET MVC perspective. In MVC we have 2 distinct objects: VIEW and ACTION. Both happens to run on the Server .

    • The ACTION can return a VIEW or ( or a redirect to) another ACTION or simply a FILE
    • The VIEW processes a Model ( and a ViewBag/ViewData) and sends the text( HTML) data to the Client .

    Instead of PAGES , we will discuss of VIEWS – because the VIEWS sends HTML data to the Client.

    So, to transfer data between View1 to View2 in MVC is reduced to this:

    a) Page1 transfer data to the server ACTION1( by a,b,c,d methods in the Preamble )

    b) The Action receives the values as his parameters ( by binding) and can do this:

    b1) Return a different View ( using some logic :

    if( a )

    return View1(Model1);

    else

    return View2(Model1);

    b2) Returning a Redirect to ACTION2 ( that return View2) or simply return the result of this action

    return RedirectToAction(Action2(<parameters>)); //Used in Post/Request/GET, http://en.wikipedia.org/wiki/Post/Redirect/Get

    return Action2(<parameters>);

    Resuming: Transfer betweem PAGE to PAGE in ASP.NET MVC is really transfering from ACTION to ACTION , besides the cookie that can be transferred directly by the browser.

    9 Modalities to transfer data from Page to Page in ASP.NET MVC

    Enough theory, let’s do some code. We have a Model to transfer named ModelTransfer

    public class ModelTransfer
        {
            public int Age { get; set; }
            public string Name { get; set; }
    
    
        }
    

    We have the first View1( Index) and a second View2(Transfer) that will server as an example. Also, we will have the more ACTIONS – one for each example of transfer – all are using the TRANSFER action as an ultimate resort do see the View.

    Method1 : Transfer directly to the second View/Action .

    <a href='@Url.Action("Transfer", new { Age = 42, Name = "Andrei Ignat" })'>click me</a>
    
    public ActionResult Transfer(ModelTransfer m)
    

    Method2 Index sends POST data to a [HttpPost] Index action, that performs some calculations and return a redirect.Usefull in PRG

    @using (Html.BeginForm()) { 
    <input type="text" id="Age" name="Age" value="42" />
    <input type="text" id="Name" name="Name"  value="Andrei Ignat"/>
    <input type="submit" value="Click me" />
    }
    
    
     [HttpPost]
            public ActionResult Index(ModelTransfer m)
            {
                //save to the database the data 
    
                //this is for transferring alert data - such an "Completed saving" message to the user 
                TempData["displayalert"] = " this is from Index POST action!";
                //used in PRG 
                return RedirectToAction("Transfer", new ModelTransfer() { Age = m.Age, Name = m.Name });            
            }
    

    Method3: No data send. The ServerAction just make some data to be transferred to the Transfer view, by TempData

    <a href='@Url.Action("ServerAction")'>click me</a>
    
     public ActionResult ServerAction()
            {
                //You can put also into the Session / Application /Cache depending on your specifications
                TempData["MyModel"]=new ModelTransfer(){ Age = 42, Name = "Andrei Ignat"};
                TempData["displayalert"] = "this is from Server action!";
                
                return RedirectToAction("Transfer");
            }
    

    Method4: No data send. The ServerAction just make some data to be transferred to the Transfer view, by Cache

    Method5: No data send. The ServerAction just make some data to be transferred to the Transfer view, by Session

    Method6: No data send. The ServerAction just make some data to be transferred to the Transfer view, by Application

    Method7: No data send. The ServerAction just make some data to be transferred to the Transfer view, by HttpContext Items

    Method8: By Cookies

    <a href='@Url.Action("TransferCookies")'>click me</a>
    
     HttpCookie cook = new HttpCookie("Transfer");
                //usually you put here more , but now I do not want to interfere with other methods
                cook.Expires = DateTime.Now.AddSeconds(1);
                cook.Value = "from transfer cookies";
                Response.Cookies.Add(cook);
                return RedirectToAction("Transfer");
    

    Method9: By Javascript /Ajax.
    It is an entire post by itself and you can see here:
    http://msprogrammer.serviciipeweb.ro/2011/12/05/jquery-ajax-request-and-mvcdetailed/

    Summary

    In this post you have seen 9 methods to transfer data in MVC. As a bonus, the page dispolays also a message with Javascript( usefull for messaging like “Data Saved to database” messages to the user.
    The code source you will find here:

    Transfer Data Page to Page
    It is made with Razor and MVC3 – but you can replace Razor with aspx and MVC3 with MVC2 also.

    If you think I can improve this post, please leave some comment.

    Notes:

    I used here hard coding values. Please learn about T4MVC and Html.EditorFor !

    To learn more about ASP.NET MVC visit http://asp.net/mvc.

    Default TempDataProvider is based on Session. There is one more , based on cookies.

    Please do the exercises to gain self knowledge about MVC

    Andrei Ignat weekly software news(mostly .NET)

    * indicates required

    Please select all the ways you would like to hear from me:

    You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

    We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.